PIC PWM y el uso de PostScaler

3

Estoy trabajando en un proyecto con un SG90 servomotor , que requiere el uso de PWM. Sin embargo, el motor funciona con una frecuencia PWM de 50 Hz (periodo de 20 ms) y en mi PIC16F690 , cuando intento calcular un valor para el registro PR2 (por página 129 ), siempre obtengo un valor por encima de 255, hasta que reduzco F osc a menos de 1 MHz, y también uso un prescaler de 16.

Entonces, la pregunta es: ¿es posible usar el escalador posterior para lograr los resultados deseados?

Gracias

    
pregunta FourZeroFive

4 respuestas

2

No, desafortunadamente el periférico PWM no hará lo que quieres porque no puede funcionar tan lento. La mejor opción siguiente es usar el modo de comparación en su lugar y usar una rutina de servicio de interrupción para manejar el servicio de PWM.

Configure el TIMER1 para que se ejecute en, por ejemplo, 1MHz utilizando la fuente de reloj Fosc / 4 y el preescalador apropiado. Luego configure el módulo PWM en el modo de comparación y configúrelo para alternar la salida del CCP1. Establezca el CCPR1 en el número de microsegundos en su período de "encendido" de PWM. P.ej. para un 75% de trabajo a 50Hz, configure CCPR1 a 15000. También active la interrupción CCP1IF y escriba una rutina de servicio de interrupción que escriba el valor del complemento en el registro CCPR1. P.ej. para un 75% de servicio a 50Hz, el CCPR1 debe cambiarse de 15000 a 20000-15000 = 5000. La próxima vez que se active la interrupción, se hará lo contrario, estableciendo el registro CCPR1 de nuevo en 20000-5000 = 15000.

Necesitará manejar casos cerca del 0% y del 100% de manera diferente (por ejemplo, si < 2% o > 98%, deshabilite el conmutador y configure el pin apropiadamente), pero luego debe darle la señal de PWM que necesitas en el pin CCP1.

    
respondido por el Heath Raftery
2

No, no puede configurar el hardware PWM incorporado a una frecuencia tan baja como 50 Hz a la velocidad de reloj completa del procesador.

Si eso es lo que realmente quieres hacer, solo configura una interrupción periódica de 20 ms y haz el PWM tú mismo. Puede usar el temporizador 1 en modo de disparo único, permitiendo 16 bits de contador en lugar de los 10 que obtiene con el módulo PWM. Recuerde que mientras el período de repetición es de 20 ms, la duración del impulso solo varía de 0 a 2 ms.

    
respondido por el Olin Lathrop
1

Simplemente puedes usar un temporizador con una interrupción.

Configura el temporizador para que se llame tu interrupción en un intervalo conveniente, digamos 100Us.

La interrupción puede tener este aspecto

#define TIMING 200
int ratio=50; // %
void interrupt()
{
    // reset interrupt flag

   static unsigned int timing100Us=0; // or long 
   timing100Us++;

   if (timingMs == ((unsigned long)(TIMING * ratio))/100)) { // be careful of not overflowing
       // toggle pin up
   }
   else if (timingMs == TIMING) {
        // toggle pin down
        timingMs=0;
   } 
}

var se declara como una estática dentro de la función, también puedes declararla afuera como una variable global, pero la encuentro más limpia de esta manera.

El código no se ha probado, pero muestra el concepto.

    
respondido por el Damien
1

@FourZeroFive

Este código puede controlar cuatro servos de tipo pasatiempo modulado por impulsos como el SG90. Es una implementación complicada, por lo que es posible que no puedas ver cómo funciona.

Este es un ejemplo, no un tutorial sobre cómo usar el PICM PW16F690 para controlar los servos RC hobby.

/*  
 *  Date: 2018-SEPTEMBER-12
 *  File: main.c 
 *  Target: PIC16F690
 *  MPLAB: 8.92
 *  Compiler: XC8 v1.45
 *  Application: Use PWM to drive two RC servos 
 *  
 *                      PIC16F690
 *              +----------:_:----------+
 *    PWR ->  1 : VDD               VSS : 20 <- GND
 *        <>  2 : RA5       PGD/AN0/RA0 : 19 <> PGD
 *        <>  3 : RA4/AN3   PGC/AN1/RA1 : 18 <> PGC
 *    VPP ->  4 : RA3/VPP       AN2/RA2 : 17 <>
 * SERVO1 <-  5 : RC5/P1A       AN4/RC0 : 16 <>
 * SERVO2 <-  6 : RC4/P1B       AN5/RC1 : 15 <>
 * SERVO3 <-  7 : RC3/P1C       P1D/RC2 : 14 -> SERVO4
 *        <>  8 : RC6               RB4 : 13 <>
 *        <>  9 : RC7           RXD/RB5 : 12 <> RXD
 *    TXD <> 10 : RB7/TXD           RB6 : 11 <>
 *              +-----------------------:
 *                     DIP-20
 *  
 *  Use ECCP in PWM mode with pulse steering to one of four outputs
 */

#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF

#include <xc.h>

/* Helper macros */
#define clear_bit( reg, bitNumb )   ((reg) &= ~(1 << (bitNumb)))
#define set_bit( reg, bitNumb )     ((reg) |= (1 << (bitNumb)))
#define test_bit( reg, bitNumb )    ((reg) & (1 << (bitNumb)))
#define clear_wdt() CLRWDT()
#define nop() NOP()

/*  HITECH PICC-Lite specific notes:
 *  
 *  To get the constants to be computed correctly by
 *  the compiler we specify all constants to be 
 *  UNSIGNED LONG. To do this we appended "ul" to the
 *  constants used in these expressions.
 *  
 *  HITECH PICC has a "feature" that does not honor
 *  an explicit type cast of these kinds of constant
 *  expressions.
 */  

/*  
 *  These defines are used to configure this application for your needs.
 *  
 *  TMR2_PRESCALE
 *  PWM_MAX
 *  FOSC
 *  NUMBER_OF_SERVOS
 *  
 *  The default values are tuned for a 4MHz crystal and four servos.
 *  
 *  After servo has completed their pulse we need to take one
 *  PWM period to update the servo multiplexer.
 *  
 *  You need to make sure that time period for updating all the servos
 *  pulse an extra PWM period for each servo is less than 20 milliseconds.
 *  
 *  Making PWM_MAX smaller will make the extra PWM period for each servo shorter.
 *  The trade-off is that we get interrupted more often to service TMR2.
 *  
 */  

/*  PWM_MAX must be >= 1 and <= 255.
 *  small values (< 100) will cause the TMR2 interrupt
 *  to occur so often they are kind of useless.
 */  
#define TMR2_PRESCALE 1
#define PWM_MAX (250ul)
#define FOSC 4000000ul
#define NUMBER_OF_SERVOS 4    
#define TOSC_ns (1000000000ul / FOSC)
#define PWM_PERIOD_us ((TOSC_ns * 4ul * TMR2_PRESCALE * PWM_MAX) / 1000ul)

/*  The update rate for our servos is 22.222 milliseconds. (45Hz)
 *  So this sets the number of PWM ticks in 22,222 microseconds
 *  SERVO_UPDATE_IN_PWM_PERIODS must be <= 65535 to fit into an unsigned int.
 */  
#define SERVO_UPDATE_IN_PWM_PERIODS ((22222ul + (PWM_PERIOD_us / 2)) / PWM_PERIOD_us)

/*  Position min and max are 16-bit unsigned values
 *  
 *  Define the limits for a particular servo
 *  The servo rotates to the "left" limit for a pulse of 0.800 milliseconds.
 * The servo rotates to the "right" limit for a pulse of 2.200 milliseconds.
 */  
#define POSITION_MIN ((800ul  * (PWM_MAX * 4ul)) / PWM_PERIOD_us)
#define POSITION_MAX ((2200ul * (PWM_MAX * 4ul)) / PWM_PERIOD_us)

/*  Timer 2 interrupt handler 
 *  
 *  This handler updates PWM one and PWM two
 *  for a typical pulse output to control a
 *  common analog model radio control servo
 *  
 *  This example code uses a T2 prescaler of 1:1
 *  and a PWM_MAX of 250. This means that half of
 *  the available clock cycles are used just for
 *  Timer 2 interrupt service.
 *  
 *  As the value for PWM_MAX gets closer to the 
 *  worst case time a larger portion of real time
 *  is used just to deal with the Timer 2 interrupt.
 *  
 *  As the value for PWM_MAX gets closer to the 
 *  best case time the ISR will fail and all of 
 *  real time is spent thrashing in the ISR.
 *  
 */  

unsigned int servo_bank[NUMBER_OF_SERVOS];

unsigned int update_tick_count;
bit servo_updated;
bit servo_bank_output_complete;
bit servo_select_next;

unsigned char servo_select;

static unsigned char T2_handler(void)
{   
 static unsigned int temp_period_one;

 if (TMR2IE)   /* test if T2 interrupt is enabled */
 {  
   if (TMR2IF) /* test if T2 interrupt is asserted */
   {
     TMR2IF = 0;  /* reset T2 assertion */

     if(servo_select_next)
     {
       servo_select_next = 0;
       if (++servo_select < NUMBER_OF_SERVOS)
       {
         PSTRCON = (PSTRCON & 0xF0) | ((PSTRCON << 1) & 0x0F);
         temp_period_one = servo_bank[servo_select];
       }
     }

     if (servo_bank_output_complete)
     {
       {
         servo_bank_output_complete = 0;
         servo_select_next = 1;
       }
     }

     if (update_tick_count == 0)
     {
       update_tick_count = SERVO_UPDATE_IN_PWM_PERIODS;
       servo_select = 0;
       PSTRCON = 0b00010001;   /* PWM pulse steering P1A is first servo */
       temp_period_one = servo_bank[0];
       servo_updated = 1;
     }
     --update_tick_count;

     DC1B1 = 0;
     DC1B0 = 0;
     if (temp_period_one > (PWM_MAX * 4))
     {
       CCPR1L = PWM_MAX;
       temp_period_one -= (PWM_MAX * 4);
     }
     else
     {

       if (temp_period_one != 0)
       {
         servo_bank_output_complete = 1;
         if (test_bit(temp_period_one,0)) DC1B0 = 1;
         if (test_bit(temp_period_one,1)) DC1B1 = 1;
         temp_period_one >>= 2;
         CCPR1L  = (unsigned char)(temp_period_one);
         temp_period_one = 0;
       }
       else
       {
         CCPR1L  = 0;
       }
     }
     return(1); /* interrupt serviced return value */
   }
 }  
 return(0); /* interrupt not serviced return value */
}   

/* initialize the PWM and interrupt on T2 overflow */
void T2_init(void)
{   
    PR2 = PWM_MAX-1;        /* set PWM period */
    TMR2IE = 0;             /* turn off TMR2 interrupt */
    TRISC &= (0b11000011);  /* make RC2,RC3,RC4,RC5 outputs for PWM */
    PORTC &= (0b11000011);  /* make all PWM outputs low */

#if (TMR2_PRESCALE == 1)
    T2CON = 0b00000000; /* postscaler 1:1 */
                        /* TMR2 off */
                        /* prescaler 1:1 */
#endif
#if (TMR2_PRESCALE == 4)
    T2CON = 0b00000001; /* postscaler 1:1 */
                        /* TMR2 off */
                        /* prescaler 1:4 */
#endif
#if (TMR2_PRESCALE == 16)
    T2CON = 0b00000010; /* postscaler 1:1 */
                        /* TMR2 off */
                        /* prescaler 1:16 */
#endif

    TMR2 = 0;

    CCP1CON = 0b00001100;   /* PWM mode */
    CCPR1L  = 0;
    PSTRCON = 0b00010000;   /* PWM pulse steering no output */

    update_tick_count = 0;
    servo_updated = 0;
    servo_select_next = 0;
    for (servo_select=0; servo_select < NUMBER_OF_SERVOS; ++servo_select)
    {
        servo_bank[servo_select] = POSITION_MAX;
    }

    TMR2ON = 1;   /* turn on TMR2 */
    TMR2IE = 1;   /* turn on TMR2 interrupt */
}   

/*  
 *  Interrupt vector starts here
 */  

static void interrupt isr(void)
{   
    if (T2_handler()) return;
}   

/*  This function is part of the 
 *  test and verification code.
 *  
 *  It uses global variables for
 *  input and output. 
 *  
 *  This is not a good style for C programs but
 *  does build smaller, faster code for a PIC.
 *  
 */  
unsigned int servo_period;
bit dir; 
unsigned int delta;
static void update_position(void)
{
 if (dir)
 { /*  count up */
   if (servo_period >= (unsigned int)(POSITION_MAX))
   {
     servo_period -= delta;
     dir = 0;
   }
   else
   {
     servo_period += delta;
   }
 }
 else
 { /*  count down */
   if (servo_period <= (unsigned int)(POSITION_MIN))
   {
     servo_period += delta;
     dir = 1;
   }
   else
   {
     servo_period -= delta;
   }
 }
}

void main (void)
{
    unsigned int servo_one;
    unsigned int servo_two;
    static bit servo_dir_one;
    static bit servo_dir_two;

    OSCCON = 0b01100000;  /*  Select 4MHz internal oscillator */
    /*  disable all interrupt sources */
    INTCON = 0x00;
    ADCON0 = 0x00;  /*  turn off ADC module */
    ANSEL  = 0;     /*  set GPIOs for digital I/O */
    ANSELH = 0;
    PIE1 = 0x00;
    PIE2 = 0x00;

    OPTION_REG = 0b11011110;  /*  Weak pull ups disabled */
                          /*  Interrupt on rising edge of RB0/INT pin */
                          /*  T0 internal clock source */
                          /*  T0 clock edge high-to-low */
                          /*  Prescaler assigned to WDT */
                          /*  Prescale 1:64 for WDT */


    /*  initialize my interrupt handlers */
    T2_init();

    /*  turn on the interrupt system */
    PEIE = 1;
    GIE  = 1;

/*  This is a simple servo movement test
 *  that runs two servos up and down at
 *  different rates.
 *  
 *  Servo one is the first servo in the update sequence.
 *  Servo two is the second servo in the update sequence.
 *  
 *  The cycle rates are:
 *    for servo one is about 1.66 seconds.
 *    for servo two is about 4.28 seconds.
 */  
#define DELTA_ONE ((POSITION_MAX - POSITION_MIN) / 83ul)
#define DELTA_TWO ((POSITION_MAX - POSITION_MIN) / 214ul)

    servo_one = POSITION_MAX;
    servo_two = POSITION_MIN;
    servo_dir_one  = 0; /*  set servo one to count down to start */
    servo_dir_two  = 1; /*  set servo two to count up to start */

    while(1)
    {
      if (servo_updated)
      {
        clear_wdt();
        /*  start critical section. */
        /*  do not allow PWM interrupts */
        /*  while updating servo position array. */
        TMR2IE = 0;
        servo_bank[0] = servo_one;
        servo_bank[1] = servo_two;
        TMR2IE = 1;
        /*  end critical section. */

        servo_updated = 0;

        servo_period = servo_one;
        dir = servo_dir_one;
        delta = DELTA_ONE;
        update_position();
        servo_one = servo_period;
        servo_dir_one = dir;

        servo_period = servo_two;
        dir = servo_dir_two;
        delta = DELTA_TWO;
        update_position();
        servo_two = servo_period;
        servo_dir_two = dir;

      }
   }
}
    
respondido por el Dan1138

Lea otras preguntas en las etiquetas