# PIC12F683 PWM Problem

#### 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,534
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...

Joined Jul 18, 2013
22,041
The calculator shows the lower limit to be 250Hz with those numbers.
Max.

#### krlngc

Joined Dec 9, 2013
11
Oh I see now. Thank you very much for your replies.

#### MMcLaren

Joined Feb 14, 2010
851
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                                     *
*     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%
}                          //
}                            //

Cheerful regards, Mike

Last edited:

#### BobTPH

Joined Jun 5, 2013
2,748
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

#### 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                                     *
*     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%
}                          //
}                            //

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
851
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.

Cheerful regards, Mike

Last edited: