# PIC12F683 PWM Problem

Discussion in 'Embedded Systems and Microcontrollers' started by krlngc, Jun 19, 2014.

1. ### krlngc Thread Starter New Member

Dec 9, 2013
11
0
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
Code ( (Unknown Language)):
1.
2. void InitPWM()
3. {
4.     OSCCONbits.SCS=1;   //Internal oscillator is used for system clock
5.     OSCCONbits.IRCF=0b110; // Internal Oscillator Frequency 4 MHz
6.     CCP1CON=0x2C;          // bit3-0 PWM Mode
7.     TRISIO2=0;             // CCP1 output
8.     T2CON=0x06;            // Timer2 is on , Prescale 1:16
9.     PR2=1249;              // 20 ms = (PR +1) *4 * 250ns * TMR2 Prescale value
10.     CCPR1L=0b01;           //
11. }
12.
Thank you

Jul 18, 2013
10,266
2,269
3. ### tshuck Well-Known Member

Oct 18, 2012
3,531
675
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...

Jul 18, 2013
10,266
2,269
The calculator shows the lower limit to be 250Hz with those numbers.
Max.

5. ### krlngc Thread Starter New Member

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

6. ### MMcLaren Well-Known Member

Feb 14, 2010
756
115
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;

Code ( (Unknown Language)):
1.
2. /********************************************************************
3.  *                                                                  *
4.  *  Project: 12F683 PWM #1                                          *
5.  *   Source: 12F683_PWM_#1.c                                        *
6.  *   Author: Mike McLaren, K8LH                                     *
8.  *     Date: 19-Jun-2014                                            *
9.  *                                                                  *
10.  *  12F683 PWM Demo, PWM output on GP2/CCP1 (pin 5)                 *
11.  *                                                                  *
12.  *                                                                  *
13.  *      IDE: MPLAB v8.92 (tabs = 4)                                 *
14.  *     Lang: Microchip XC8 v1.31                                    *
15.  *                                                                  *
16.  ********************************************************************/
17.
18.    #include <xc.h>
19.
20.    #pragma config FOSC = INTOSCIO, MCLRE = OFF, WDTE = OFF
21.
22.
23.    #define _XTAL_FREQ 4000000
24.
25.   /******************************************************************
26.    *  function prototypes                                           *
27.    ******************************************************************/
28.   /******************************************************************
29.    *  type definitions                                              *
30.    ******************************************************************/
31.   /******************************************************************
32.    *  variables and constants                                       *
33.    ******************************************************************/
34.
35.    unsigned char frame = 1;     // ISR PWM "frame" number, 0..80
36.    unsigned int pulse = 0;      // ISR PWM "pulse" work variable
37.
38.    unsigned int width = 2000;   // pulse width, 0..20,000 usecs
39.
40.   /******************************************************************
41.    *  low level drivers                                             *
42.    ******************************************************************/
43.   /******************************************************************
44.    *  functions                                                     *
45.    ******************************************************************/
46.
47.    void setdutycycle(unsigned int dcyvalue)
48.    { INTCONbits.GIE = 0;        // suspend interrupts
49.      width = dcyvalue;          // update 'width', 0..20000 (usecs)
50.      INTCONbits.GIE = 1;        // restore interrupts
51.    }                            //
52.
53.   /******************************************************************
54.    *  main init                                                     *
55.    ******************************************************************/
56.
57.    void main()
58.    { ANSEL = 0;                 // make pins digital
59.      TRISIO = 0;                // all outputs
60.      GPIO = 0;                  // all output latches low
61.      OSCCON = 0b01100010;       // initialize 4-MHz INTOSC
62.      while(!HTS);               // wait until OSC stable
63.   /*                                                                *
64.    *  setup PWM and TMR2 for 250-usec PWM period "frames"           *
65.    *                                                                */
66.      TMR2 = 0;                  // clear TMR2
67.      PR2 = 250-1;               // 250-usec period
68.      PIE1 = 0;                  // clear peripheral interrupt enables
69.      PIR1 = 0;                  // clear peripheral interrupt flags
70.      T2CON = 0b00000000;        // 00000000
71.                                 // -0000--- TOUTPS, postscale 1:1
72.                                 // -----0-- TMR2ON, turn TMR2 off
73.                                 // ------00 T2CKPS, prescale 1
74.      CCP1CON = 0b00001100;      // 00001100
75.                                 // --00---- DC1B, duty cycle lsb's
76.                                 // ----1100 CCP1M, PWM active high
77.      PIE1bits.T2IE = 1;         // enable TMR2 interrupts
78.      INTCONbits.GIE = 1;        // enable global interrupts
79.      INTCONbits.PEIE = 1;       // enable peripheral interrupts
80.      T2CONbits.TMR2ON = 1;      // start TMR2
81.
82.
83.   /******************************************************************
84.    *  main loop                                                     *
85.    ******************************************************************/
86.
87.      while(1)                   //
88.      {                          //
89.      }                          //
90.    }                            //
91.
92.   /******************************************************************
93.    *  interrupt service routine                                     *
94.    ******************************************************************/
95.
96.    void interrupt isr()         // 250-usec TMR2 interrupts
97.    {                            //
98.      PIR1bits.TMR2IF = 0;       // clear TMR2 interrupt flag
99.      if(--frame == 0)           // if end of 20 msec period
100.      { frame = 80;              // reset for new 20 msec period
101.        pulse = width;           // reset 'pulse' work variable
102.      }                          //
103.      if(pulse > 250)            // if remaining pulse > 250 usecs
104.      { CCPR1L = 250;            // do a 100% duty cycle frame
105.        pulse -= 250;            // adjust balance of 'pulse'
106.      }                          //
107.      else                       // do a variable or 0% frame
108.      { CCPR1L = pulse;          // 0..250 usecs
109.        pulse = 0;               // force remaining frames to 0%
110.      }                          //
111.    }                            //
112.
113.

Cheerful regards, Mike

Last edited: Jun 19, 2014
7. ### BobTPH Active Member

Jun 5, 2013
768
107
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

8. ### krlngc Thread Starter New Member

Dec 9, 2013
11
0

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: Jun 20, 2014
9. ### MMcLaren Well-Known Member

Feb 14, 2010
756
115
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: Jun 20, 2014
10. ### krlngc Thread Starter New Member

Dec 9, 2013
11
0
I've handled the problem. Thank you for your help.

Kind regards,
Efe