PIC-PWM und die Verwendung von PostScaler

Ich arbeite an einem Projekt mit einem SG90- Servomotor, der die Verwendung von PWM erfordert. Der Motor arbeitet jedoch mit einer PWM-Frequenz von 50 Hz (20 ms Periode), und in meinem PIC16F690 erhalte ich immer einen Wert über 255, wenn ich versuche, einen Wert für das PR2- Register zu berechnen (siehe Seite 129 ), bis ich die reduziere F osc auf weniger als 1 MHz und verwenden Sie außerdem einen Prescaler von 16.

Die Frage ist also: Ist es möglich, Post-Scaler zu verwenden, um meine gewünschten Ergebnisse zu erzielen?

Danke

Antworten (4)

Nein, leider macht das PWM-Peripheriegerät nicht das, was Sie wollen, weil es nicht so langsam laufen kann. Die nächstbeste Option besteht darin, stattdessen den Vergleichsmodus zu verwenden und eine Interrupt-Service-Routine zu verwenden, um die PWM-Eingabe zu handhaben.

Konfigurieren Sie TIMER1 so, dass er beispielsweise mit 1 MHz läuft, indem Sie die Fosc/4-Taktquelle und den entsprechenden Vorteiler verwenden. Stellen Sie dann das PWM-Modul in den Vergleichsmodus und konfigurieren Sie es so, dass es den CCP1-Ausgang umschaltet. Stellen Sie den CCPR1 auf die Anzahl der Mikrosekunden in Ihrer PWM-Einschaltperiode ein. Z.B. für 75 % Einschaltdauer bei 50 Hz CCPR1 auf 15000 setzen. Außerdem den CCP1IF-Interrupt freigeben und eine Interrupt-Service-Routine schreiben, die den Komplementwert in das CCPR1-Register schreibt. Z.B. für 75 % Auslastung bei 50 Hz sollte der CCPR1 von 15000 auf 20000-15000 = 5000 geändert werden. Wenn der Interrupt das nächste Mal ausgelöst wird, wird er das Gegenteil bewirken und das CCPR1-Register auf 20000-5000 = 15000 zurücksetzen.

Sie müssen Fälle nahe 0% und 100% unterschiedlich handhaben (z. B. wenn <2% oder>98%, dann deaktivieren Sie den Schalter und setzen Sie den Pin entsprechend), sollten Ihnen dann aber das PWM-Signal geben, das Sie am CCP1 benötigen Stift.

Nein, Sie können die eingebaute PWM-Hardware nicht auf eine Frequenz von nur 50 Hz bei voller Prozessortaktrate einstellen.

Wenn Sie das wirklich möchten, richten Sie einfach einen periodischen 20-ms-Interrupt ein und führen Sie die PWM selbst durch. Sie können Timer 1 im One-Shot-Modus verwenden und 16 Zählerbits anstelle der 10, die Sie mit dem PWM-Modul erhalten, zulassen. Denken Sie daran, dass die Wiederholungsperiode zwar 20 ms beträgt, die Impulslänge jedoch nur zwischen 0 und 2 ms variiert.

Ihre Eröffnungsaussage ist zu 100% genau, könnte jedoch zu der Annahme verleiten, dass die PWM nicht zur Erzeugung von Impulsen für Hobby-RC-Servos verwendet werden kann. Die Vorzüge der Verwendung von TIMER1 und CPP oder TIMER2 und PWM wurden viel diskutiert. Siehe: CCP PWM vs PWM SW with interrupts link und PWM using pic16f877a link Diese Threads besprechen die Vor- und Nachteile jeder Methode.

Sie können einfach einen Timer mit einem Interrupt verwenden.

Stellen Sie den Timer so ein, dass Ihr Interrupt in einem geeigneten Intervall aufgerufen wird, sagen wir 100Us.

Der Interrupt kann so aussehen

#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 wird innerhalb der Funktion als statisch deklariert, Sie können es auch außerhalb als globale Variable deklarieren, aber ich finde es auf diese Weise sauberer.

Code ist nicht getestet, zeigt aber das Konzept.

@FourZeroFive

Dieser Code kann vier pulsmodulierte Hobby-Servos wie das SG90 steuern. Es ist eine komplizierte Implementierung, sodass Sie möglicherweise nicht sehen können, wie sie funktioniert.

Dies ist ein Beispiel, kein Tutorial zur Verwendung des PIC16F690 PWM zur Steuerung von RC-Hobby-Servos.

/*  
 *  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;

      }
   }
}