PIC12F683 PWM Problem

Thread Starter

krlngc

Joined Dec 9, 2013
11
Hello,

I aim to produce PWM with 20ms period and 2ms duty cycle. I use P12F683 @4MHz, MpLab xc8. I calculated the PR2 value given by data sheet;

PWM Period = [(PR2) + 1] • 4 • Tosc • TMR2 Prescale Value

However when I do the simulation on Proteus, PWM's period comes out shorter than I expect it to be. It's 3.7ms. I also tried some random values for PR2 in order to approach 20ms, none of them worked. I can't really understand what causes the problem. I'd be grateful if you could share your opinions.

Here's the function I use for initializing PWM
Rich (BB code):
void InitPWM()
{
    OSCCONbits.SCS=1;   //Internal oscillator is used for system clock
    OSCCONbits.IRCF=0b110; // Internal Oscillator Frequency 4 MHz
    CCP1CON=0x2C;          // bit3-0 PWM Mode
    TRISIO2=0;             // CCP1 output
    T2CON=0x06;            // Timer2 is on , Prescale 1:16
    PR2=1249;              // 20 ms = (PR +1) *4 * 250ns * TMR2 Prescale value
    CCPR1L=0b01;           //
}
Thank you
 

tshuck

Joined Oct 18, 2012
3,535
PR2 is an 8-bit register, and you've supplied 1249. This is truncated to the lower 8-bit value, so 1249 becomes 225. Putting 225 in your equation yields:

Period = 250ns*4*[225 + 1]*TMR2 Prescale ~= 3.7ms.

Recalculate your value for an 8-bit PR2...
 

MMcLaren

Joined Feb 14, 2010
842
Setting up the PWM module for a very low frequency period, 50-Hz (20,000 usecs) in this case, is somewhat difficult. Instead, consider breaking up that 20,000 usec period into smaller PWM periods or "frames". For example, use twenty 1,000 usec (1-KHz) PWM period "frames", or use eighty 250-usec (4-KHz) PWM period "frames", to make up the much longer 20,000 usec period.

Since the CCPR1L "duty cycle" register is double-buffered, that is, the value you place into it is used by the PWM module in the "next" PWM cycle, you can use a small ISR "helper" to load the duty cycle register and keep track of the overall duty cycle pulse and 20,000 usec period during each PWM interrupt cycle.

This method can be used to produce a high resolution pulse width of 0 to 20,000 usecs in a very low 50-Hz period. Here's a quick-n-dirty (and untested) example in XC8 for 12F683 with 1-usec pulse width resolution;

Rich (BB code):
/********************************************************************
 *                                                                  *
 *  Project: 12F683 PWM #1                                          *
 *   Source: 12F683_PWM_#1.c                                        *
 *   Author: Mike McLaren, K8LH                                     *
 *  (C)2013: Micro Application Consultants, All Rights Reserved     *
 *     Date: 19-Jun-2014                                            *
 *                                                                  *
 *  12F683 PWM Demo, PWM output on GP2/CCP1 (pin 5)                 *
 *                                                                  *
 *                                                                  *
 *      IDE: MPLAB v8.92 (tabs = 4)                                 *
 *     Lang: Microchip XC8 v1.31                                    *
 *                                                                  *
 ********************************************************************/

   #include <xc.h>

   #pragma config FOSC = INTOSCIO, MCLRE = OFF, WDTE = OFF


   #define _XTAL_FREQ 4000000

  /******************************************************************
   *  function prototypes                                           *
   ******************************************************************/
  /******************************************************************
   *  type definitions                                              *
   ******************************************************************/
  /******************************************************************
   *  variables and constants                                       *
   ******************************************************************/

   unsigned char frame = 1;     // ISR PWM "frame" number, 0..80
   unsigned int pulse = 0;      // ISR PWM "pulse" work variable

   unsigned int width = 2000;   // pulse width, 0..20,000 usecs

  /******************************************************************
   *  low level drivers                                             *
   ******************************************************************/
  /******************************************************************
   *  functions                                                     *
   ******************************************************************/

   void setdutycycle(unsigned int dcyvalue)
   { INTCONbits.GIE = 0;        // suspend interrupts
     width = dcyvalue;          // update 'width', 0..20000 (usecs)
     INTCONbits.GIE = 1;        // restore interrupts
   }                            //

  /******************************************************************
   *  main init                                                     *
   ******************************************************************/

   void main()
   { ANSEL = 0;                 // make pins digital
     TRISIO = 0;                // all outputs
     GPIO = 0;                  // all output latches low
     OSCCON = 0b01100010;       // initialize 4-MHz INTOSC
     while(!HTS);               // wait until OSC stable
  /*                                                                *
   *  setup PWM and TMR2 for 250-usec PWM period "frames"           *
   *                                                                */
     TMR2 = 0;                  // clear TMR2
     PR2 = 250-1;               // 250-usec period
     PIE1 = 0;                  // clear peripheral interrupt enables
     PIR1 = 0;                  // clear peripheral interrupt flags
     T2CON = 0b00000000;        // 00000000
                                // -0000--- TOUTPS, postscale 1:1 
                                // -----0-- TMR2ON, turn TMR2 off
                                // ------00 T2CKPS, prescale 1
     CCP1CON = 0b00001100;      // 00001100
                                // --00---- DC1B, duty cycle lsb's
                                // ----1100 CCP1M, PWM active high
     PIE1bits.T2IE = 1;         // enable TMR2 interrupts
     INTCONbits.GIE = 1;        // enable global interrupts
     INTCONbits.PEIE = 1;       // enable peripheral interrupts
     T2CONbits.TMR2ON = 1;      // start TMR2


  /******************************************************************
   *  main loop                                                     *
   ******************************************************************/

     while(1)                   //
     {                          //
     }                          //
   }                            //

  /******************************************************************
   *  interrupt service routine                                     *
   ******************************************************************/

   void interrupt isr()         // 250-usec TMR2 interrupts
   {                            //
     PIR1bits.TMR2IF = 0;       // clear TMR2 interrupt flag
     if(--frame == 0)           // if end of 20 msec period
     { frame = 80;              // reset for new 20 msec period
       pulse = width;           // reset 'pulse' work variable
     }                          //
     if(pulse > 250)            // if remaining pulse > 250 usecs
     { CCPR1L = 250;            // do a 100% duty cycle frame
       pulse -= 250;            // adjust balance of 'pulse'
     }                          //
     else                       // do a variable or 0% frame
     { CCPR1L = pulse;          // 0..250 usecs
       pulse = 0;               // force remaining frames to 0%
     }                          //
   }                            //
Good luck on your project.

Cheerful regards, Mike
 
Last edited:

BobTPH

Joined Jun 5, 2013
2,057
Alternately, you could just run the clock slower. That would allow for longer PWM periods. If you run at 500KHz you will be able to get a 20ms period.

You will, however get better resolution on the angle of your servo by following the directions in the previous post.

Bob
 

Thread Starter

krlngc

Joined Dec 9, 2013
11
Setting up the PWM module for a very low frequency period, 50-Hz (20,000 usecs) in this case, is somewhat difficult. Instead, consider breaking up that 20,000 usec period into smaller PWM periods or "frames". For example, use twenty 1,000 usec (1-KHz) PWM period "frames", or use eighty 250-usec (4-KHz) PWM period "frames", to make up the much longer 20,000 usec period.

Since the CCPR1L "duty cycle" register is double-buffered, that is, the value you place into it is used by the PWM module in the "next" PWM cycle, you can use a small ISR "helper" to load the duty cycle register and keep track of the overall duty cycle pulse and 20,000 usec period during each PWM interrupt cycle.

This method can be used to produce a high resolution pulse width of 0 to 20,000 usecs in a very low 50-Hz period. Here's a quick-n-dirty (and untested) example in XC8 for 12F683 with 1-usec pulse width resolution;

Rich (BB code):
/********************************************************************
 *                                                                  *
 *  Project: 12F683 PWM #1                                          *
 *   Source: 12F683_PWM_#1.c                                        *
 *   Author: Mike McLaren, K8LH                                     *
 *  (C)2013: Micro Application Consultants, All Rights Reserved     *
 *     Date: 19-Jun-2014                                            *
 *                                                                  *
 *  12F683 PWM Demo, PWM output on GP2/CCP1 (pin 5)                 *
 *                                                                  *
 *                                                                  *
 *      IDE: MPLAB v8.92 (tabs = 4)                                 *
 *     Lang: Microchip XC8 v1.31                                    *
 *                                                                  *
 ********************************************************************/

   #include <xc.h>

   #pragma config FOSC = INTOSCIO, MCLRE = OFF, WDTE = OFF


   #define _XTAL_FREQ 4000000

  /******************************************************************
   *  function prototypes                                           *
   ******************************************************************/
  /******************************************************************
   *  type definitions                                              *
   ******************************************************************/
  /******************************************************************
   *  variables and constants                                       *
   ******************************************************************/

   unsigned char frame = 1;     // ISR PWM "frame" number, 0..80
   unsigned int pulse = 0;      // ISR PWM "pulse" work variable

   unsigned int width = 2000;   // pulse width, 0..20,000 usecs

  /******************************************************************
   *  low level drivers                                             *
   ******************************************************************/
  /******************************************************************
   *  functions                                                     *
   ******************************************************************/

   void setdutycycle(unsigned int dcyvalue)
   { INTCONbits.GIE = 0;        // suspend interrupts
     width = dcyvalue;          // update 'width', 0..20000 (usecs)
     INTCONbits.GIE = 1;        // restore interrupts
   }                            //

  /******************************************************************
   *  main init                                                     *
   ******************************************************************/

   void main()
   { ANSEL = 0;                 // make pins digital
     TRISIO = 0;                // all outputs
     GPIO = 0;                  // all output latches low
     OSCCON = 0b01100010;       // initialize 4-MHz INTOSC
     while(!HTS);               // wait until OSC stable
  /*                                                                *
   *  setup PWM and TMR2 for 250-usec PWM period "frames"           *
   *                                                                */
     TMR2 = 0;                  // clear TMR2
     PR2 = 250-1;               // 250-usec period
     PIE1 = 0;                  // clear peripheral interrupt enables
     PIR1 = 0;                  // clear peripheral interrupt flags
     T2CON = 0b00000000;        // 00000000
                                // -0000--- TOUTPS, postscale 1:1 
                                // -----0-- TMR2ON, turn TMR2 off
                                // ------00 T2CKPS, prescale 1
     CCP1CON = 0b00001100;      // 00001100
                                // --00---- DC1B, duty cycle lsb's
                                // ----1100 CCP1M, PWM active high
     PIE1bits.T2IE = 1;         // enable TMR2 interrupts
     INTCONbits.GIE = 1;        // enable global interrupts
     INTCONbits.PEIE = 1;       // enable peripheral interrupts
     T2CONbits.TMR2ON = 1;      // start TMR2


  /******************************************************************
   *  main loop                                                     *
   ******************************************************************/

     while(1)                   //
     {                          //
     }                          //
   }                            //

  /******************************************************************
   *  interrupt service routine                                     *
   ******************************************************************/

   void interrupt isr()         // 250-usec TMR2 interrupts
   {                            //
     PIR1bits.TMR2IF = 0;       // clear TMR2 interrupt flag
     if(--frame == 0)           // if end of 20 msec period
     { frame = 80;              // reset for new 20 msec period
       pulse = width;           // reset 'pulse' work variable
     }                          //
     if(pulse > 250)            // if remaining pulse > 250 usecs
     { CCPR1L = 250;            // do a 100% duty cycle frame
       pulse -= 250;            // adjust balance of 'pulse'
     }                          //
     else                       // do a variable or 0% frame
     { CCPR1L = pulse;          // 0..250 usecs
       pulse = 0;               // force remaining frames to 0%
     }                          //
   }                            //
Good luck on your project.

Cheerful regards, Mike

I guess I need higher duty cycle. Because servo goes directly to -90 degree. How can I increase the duty cycle of each frame? I tried CCPR1L=250 but I couldn't get result

Thank you
Efe
 
Last edited:

MMcLaren

Joined Feb 14, 2010
842
So you're using a Servo? Did you verify that you're producing a 2000-usec pulse? If so, what did you expect the Servo to do when driven by a 2000-usec pulse?

Do you know the servo pulse width range? Five minutes of research should be enough to figure this out. Once you have the answer, adjust the "width" variable declaration for the Servo angle you want.

Good luck on your project...

Cheerful regards, Mike
 
Last edited:
Top