Fads to Obsessions and Beyond...




Free domain for life, exceptional technical support, website transfer

PIC Pulse Width Modulation (PWM)

Capture, Compare and Pulse Width Modulation (PWM) or CCP modules are found on many (most) of Microchip’s microcontrollers and are used primarily for the measurement and control of time-based pulse signals. The PWM mode (on a PIC16F876) is explored by viewing the PIC output on an oscilloscope.

Capture, Compare and Pulse Width Modulation (PWM) or CCP modules on Microchip’s microcontrollers have three modes:(1)

  • Capture Mode
    • In Capture mode, the value of Timer1 is captured/stored when an event ocurs on pin CCPx. The event is defined by a configuration setting and can be a falling edge, rising edge or every 4th or 16th rising edge.
    • Capture mode is used to measure the length of time elapsed between two events (with 'event' meaning either rising or falling edge of a signal on the CCPx pin).
  • Compare Mode
    • In Compare mode, the 16-bit CCPRx register value is constantly compared against the TMR1 register pair values. When a match occurs, the CCPx pin is either: Driven high, Driven low, Remains unchanged, or Toggles based on the module’s control bit settings. A CCP interrupt is also generated when a match occurs.
    • Compare mode is analogous to the timer function on a stopwatch, in that Compare mode counts from zero to a pre-set time and then performs the pre-set change to the CCPx pin. This mode is useful for generating specific actions at precise intervals. A timer could be used to perform the same functionality, however, it would mean preloading the timer each time. Compare mode also has the added benefit of automatically altering the state of the CCPx pin based on the way the module is set up.
  • Pulse Width Modulation (PWM) Mode
    • The CCP module produce a 10-bit resolution Pulse-Width Modulated (PWM) waveform on the CCPx pin.
    • Such applications are based on one basic principle of PWM signals – as the duty cycle of a PWM signal increases, the average voltage and power provided by the PWM increases linearly.

What is Pulse Width Modulation

The PIC microcontroller, as any microcontroller, has in-built capacity to handle digital inputs/outputs and analog inputs, but not analog outputs. PWM is the method for in effect enabling the PIC microcontroller to have an analog output. An analog output voltage is useful as this can be used to control DC motor speed, intensity of a light globe/LED, temperature of a heater element etc. Using PWM from a PIC microcontroller (or other IC's), as opposed to using a potentiometer or similar to vary voltage, not only allows easy digital control/interfacing, but is much more energy efficient as ohmic heating/voltage dissipation is not necessary.

The actual 'pulse' used in PWM is a square-wave, and with the PIC16F876 this has an amplitude of 0-5V. The 'on time' of the 5V square wave (i.e., when it is at the 5V level) is the 'pulse width'. Varying this 'pulse width' by using the PIC microcontroller settings is the 'modulation'(which just means to 'vary' something). The following diagram shows the relationship between 'on' and 'off' time, the period and the duty cycle.

  • Duty Cycle DiagramDuty Cycle Diagram

    Silver Membership registration gives access to full resolution schematic diagrams.

    Duty Cycle Diagram

To summarise, PWM is a square wave generated by the PIC microcontroller, which can have a varying 'on' and 'off' time within a cycle, but the sum of the 'on' and 'off' time remains constant for every cycle. The duty-cycle is simply the percentage of time 'on' versus the total cycle time (i.e., the period). Therefore, a 100% duty-cycle means 100% 'on' time, and since the amplitude of the square wave generated by the PIC microcontroller is 5V (for the PIC16F876 at least), this means 100% duty cycle = 5V. Similarly, a 50% duty-cycle means 50% 'on' time and would effectively produce an 2.5V output. So this shows PWM is a way of digitally encoding analog output. However, the PWM signal is still digital as at any point in time, the full DC supply at the output pin is either fully on or fully off. That is, the voltage or current is supplied to the analog load by means of a repeating series of on and off pulses, with the width of the pulses giving the value of the analog signal. This leads to what is the effect of the actual frequency of the PWM square wave?

Assume a duty cycle of 50% and a frequency of 1Hz output PWM signal from the PIC microcontroller, connected to a LED or light bulb. This would supply 5V to the LED for 0.5 seconds and then 0V for 0.5 seconds, an 'average' of 2.5V. However, the LED would not appear to be half as bright, but would appear to flicker on and off. In order to make the LED appear to be dimmer (that is, be always 'on' but not as bright), the 'on' and 'off' pulsing needs to be quicker than the human eye can register. This shows that the frequency of the PWM signal needs to be quicker than the output loads response time to the switching on and off of the signal. In the example of the LED or light bulb, the modulation frequency needs to be increased to more than about 60Hz (to enable a dimmer type effect to be produced). In terms of PWM control of motors a modulation frequency of greater than about 4kHz is used. While in a power supply/regulator type application, modulation frequency can be in the order of 100kHz. In effect, the higher the modulation frequency of the PWM signal, the greater the 'smoothing' of the pseudo-analog signal produced. This leads to the general requirement to filter the PWM signal before passing on to the analog load, by the use of a RC circuit or similar.

Generating/Using PWM with the PIC Microcontroller

Using the CSS C Compiler for PIC microcontrollers makes the generation of PWM very easy. The following code snippet is all that is required to produce a 50% duty cycle, 5kHz PWM on Pin C1 for example (the CSS C Compiler manual gives further details about the "#use PWM" library).

Code Snippet 1:


  include "PWM_PIC16F876A.h"

  #use PWM(PWM1, TIMER=2, OUTPUT=PIN_C1, FREQUENCY=5kHz, DUTY=50)
  //PWM duty cycle 50%, period 200uS, frequency 5000Hz, resolution 9bits

  void main()
  {
     int16 pulseWidth; //width set in "tenths" of a percent 1000 = 100%

     PWM_ON();
     pulseWidth=1000;
     do { 
        pwm_set_duty_percent(pulseWidth); 
        delay_ms(20); // 20ms x 1000 'steps' ~ 20seconds ramp 5-0V
        pulseWidth--;
        if(pulseWidth==0) pulseWidth=1000;   
     } while (TRUE);
  }   
   			

The following video shows the result of the previous code snippet running on a PIC16F876A. The DMM response to voltage change is much slower than the PWM frequency, and so the DMM records what is effectively a changing analog voltage.

While use of the CSS "#use PWM" library makes implementation of PWM with a PIC microcontroller easy, the downside is increased RAM/ROM usage. Available RAM/ROM is generally at a premium when using microcontrollers. So it is advantageous to know some details about the PIC part being used in order to make the best use of available RAM/ROM. The following code snippet performs the same function as the previous example, but decreases RAM/ROM usage from 7% and 3% respectively to less than 1%.

Code snippet 2:


  include "PWM_PIC16F876A.h"

  //PWM duty cycle 50%, period 200uS, frequency 5000Hz, resolution 9bits

  void main()
  {
     int16 pulseWidth; //width set in "tenths" of a percent 1000 = 100%

     setup_ccp1(CCP_PWM);   // Select CCP1 mode as PWM
     setup_timer_2(T2_DIV_BY_4,124,1);
     //     (1/10000000)*4*4*124 = 200 us or 5 kHz
     //     duty value = 0 to ((124+1)*4)-1=499 .: 499L = 100% duty
     pulseWidth=499;
     set_pwm1_duty(pulseWidth);
     do {
        delay_ms(40); // 40ms x 500 'steps' ~ 20seconds ramp 5-0V
        pulseWidth--;
        if(pulseWidth==0) pulseWidth=499;
        set_pwm1_duty(pulseWidth);
     } while (TRUE); 
  }  
   			

The trade-off with this alternative approach, which enables more effective use of available RAM/ROM, is the learning curve coming to grips with the "innards" of the PIC microcontroller, in this case, in regard to various registers required to implement PWM. Although the built-in functions of the CSS C Compiler still provide a level of abstraction and make the overall process relatively painless (as opposed to juggling registers/memory with asm for example!).

PIC16F876A PWM Module

The PIC16F876A has two CCP modules, which means two seperate PWM outputs are possible. Although from the data sheet, both PWM's will have the same frequency and update rate (as they both use TMR2). From the previous background discussion about PWM, a PWM output is just a square wave of a certain period (i.e. frequency, since 1/period = frequency) and the "on" part of the cycle being the "pulse width". This boils down to setting an output square wave to "low"(i.e. 0V) on a particular pin, counting for a certain time, then setting the output square wave to "high"(i.e. 5V), and then continue counting for a further amount of time equivalent to the total desired period, and then repeat "endlessly" (i.e. according to the PIC code loaded). So to implement this the PIC16F876A uses the PR2 register to hold the PWM period, the CCPRxL register for the pulse width (where x=1 or 2 for whichever PWM channel selected) and Timer2 to do the "counting". The following diagram summarises the process/implementation:

  • PWM Operation SchematicPWM Operation Schematic

    Silver Membership registration gives access to full resolution schematic diagrams.

    PWM Operation Schematic

This means the PWM process on the PIC involves some initial setup in the T2CON and CCP1CON registers, setting the duty cycle via CCPRxL:CCP1CON<5:4> and the PWM period via the PR2 register. The built-in functions of the CSS C Compiler help with this.

  • The function SETUP_CCP1(CCP_PWM) writes the correct bits to the CCP1CON register to select PWM mode for the PIC CCP module.
  • The function SETUP_TIMER_2(mode,PWM_period,postscale) writes the correct bits to the T2CON and PR2 registers, where mode = a value for the pre-scaler (either T2_DIV_BY_1, T2_DIV_BY_4, or T2_DIV_BY_16); PWM_period = a integer value from 0 to 255 that is the number of "ticks" before the TMR2 is reset; and postscale always = 1 as the post-scaler is not used in PWM.
  • The function SET_PWM1_DUTY(duty_value) writes the correct bits to CCPRxL:CCP1CON<5:4> where duty_value = a 8 or 16bit value representing the duty cycle

The 'PWM_period' value and the 'duty_value' are calculated as follows:

  • (eq 1) PWM period = [(PR2) + 1] x 4 x Tosc x TMR2_prescaler_value, where
    • PR2 = value from 0 to 255
    • Tosc = 1/(PIC Oscillator frequency)
    • TMR2_prescaler_value = value of either 1, 4, 16
    • value of '4' is required because four oscillator "ticks" for each TMR2 increment
  • (eq 2) duty_cycle = CCPRxL:CCP1CON x Tosc x TMR2_prescaler_value, where
    • CCPRxL:CCP1CON<5:4> = value from 0 to xxx
    • Tosc = 1/(PIC Oscillator frequency)
    • TMR2_prescaler_value = value of either 1, 4, 16

For example, if the PIC oscillator is 10MHz and the desired PWM frequency is 5kHz with a 50% duty cycle required. Firstly, better to re-arrange the PWM period formula in terms of PR2, therefore:

PR2 = (PIC_oscillator_freq/(PWM_frequency x TMR2_prescaler_value x 4)) - 1

So for the example, PIC_oscillator_freq = 10,000,000; PWM_frequency= 5,000; and the allowable values for TMR2_pre_scaler_value are either 1, 4 or 16.

    • For TMR2_prescaler_value = 1, calculated PR2 = 499; PR2 can be maximum of 255, so TRM2_prescaler = 1 cannot be used
    • For TMR2_prescaler_value = 4, calculated PR2 = 124
    • For TMR2_prescaler_value = 16, calculated PR2 = 30.25; PR2 must be integer therefore round value, means exact desired frequency cannot be achieved with this pre-scaler value.

So now knowing TMR2_prescaler value is equal to 4 for the desired PWM frequency (and the value of PR2 is 124), can then calculate the value of CCPRxL:CCP1CON<5:4> for the desired duty cycle. Remembering that the duty-cycle is simply the percentage of time 'on' versus the total cycle time, the equation given above for duty_cycle (eq 2) is really the 'on' time in seconds. So in order to calculate the value of CCPRxL:CCP1CON<5:4> for the desired duty cycle as a percentage the formula (duty_cycle/PWM_period)*100 = % duty cycle needs to be incorporated with eq 1 and eq 2 above and the rearranged in term of CCPRxL:CCP1CON<5:4> therefore:

CCPRxL:CCP1CON<5:4> = (duty cycle as percent x PWM_period)/(100 x Tosc x TMR2_prescaler_value)

So continuing with the above example, duty cycle as percent = 50, PWM_period = 0.0002 (calculated from eq 1), Tosc=0.0000001, TMR2_prescaler_value = 4 and therefore CCPRxL:CCP1CON<5:4> = 250. Notice the value of 250 'fits' within a 8-bit integer. If for alternative example, the duty cycle required was 75%, then the calculated value would have been 375. This would then require a 'long int' value within CSS C to be defined to hold the result of the calculation. There is a Excel spreadsheet available in the (see the Downloads table at the end of this section) that provides all the necessary calculations to give values for the various PIC microcontroller registers to enable PWM output.

Such calculation of 'raw' registers values required to implement PWM on the PIC leads to avoiding using CSS C PWM functions entirely, with the aim to save available RAM/ROM. The PIC16F876A datasheet states PWM operation requires the following steps:

    • Set the PWM period by writing to the PR2 register
    • Set the PWM duty cycle by writing to the CCPR1L register and CCP1CON<5:4> bits
    • Make the CCP1 pin an output by clearing the TRISC<2> bit.
    • Set the TMR2 prescale value and enable Timer2 by writing to T2CON.
    • Set the CCP1 module for PWM operation.

This leads to the following Code Snippet 3:


  include "PWM_PIC16F876A.h"
  //PWM duty cycle 50%, period 200uS, frequency 5000Hz,
  
  #byte T2CON   = 0x12
  #byte CCPR1L  = 0x15
  #byte CCP1CON = 0x17
  #byte PR2     = 0x92
  
  void main()
  { 
   int16 dutyCycle;       // note 16-bit variable, values from 0-1023
        
   PR2 = 124;             //(1/10000000)*4*4*124 = 200 us or 5 kHz
   dutyCycle = 499;       //duty value = 0 to ((124+1)*4)-1=499 .: 499L = 100%
   CCPR1L = dutyCycle>>2; //8 MSB's of duty cycle into register
   CCP1CON = 0b00001100;  //select PWM mode
   CCP1CON = CCP1CON | (dutyCycle ‹‹4); //2 LSB's of duty cycle into register
   SET_TRIS_C(0);         //set CCP1 pin as output, actually all Port C output
   T2CON = 0b0000101;     //select prescaler=4 and TMR2 set to on
   do {
       delay_ms(40);      //40ms x 500 'steps' ~ 20seconds ramp 5-0V
       dutyCycle--;
       if(dutyCycle==0) dutyCycle=499;
       CCPR1L = dutyCycle>>2;               //update duty cycle MSB's
       CCP1CON = CCP1CON | (dutyCycle ‹‹4); //update duty cycle 2 LSB's
      } while (TRUE);
   }  
                 

The following table reports ROM/RAM usage for the three code snippets (PWM implementation approaches on the PIC 16F876 using CSS C).

Code Snippet  Description  ROM Usage  RAM Usage 
1.  Using CSS C #USE PWM functions  262  24 
2.  CSS C functions, but no #USE PWM  116 
3.  Setting registers directly  119 
3a.  Setting registers directly + not using 2 LSB's ie '8 bit mode'  91 

This shows not much benefit in accessing and setting the various special function registers on the PIC directly in terms of decreasing ROM use (although most likely my implementation of moving the 2 LSB's of the duty cycle into CCP1CON<5:4> can be improved). The code snippet "3a" references deleting the lines of code "CCP1CON = CCP1CON | (dutyCycle ‹‹4);" from code snippet 3. This in effect gives "8 bit" resolution of the pulse width. The use of the various CSS C functions (i.e., code snippet 2) does give more "readable" C code compared to code snippet 3 (and this has the benefit of likely decreasing coding/logic errors). The reference to "8 bit" pulse width resolution and not using the 2 LSB's of the duty cycle is probably worth a few extra comments.

As mentioned previously, the pulse width is determined by 8bits in the CCPR1L register concantenated with 2 bits from CCP1CON<5:4> therefore giving a maximum of 10bit resolution. However, the actual effective realisable resolution depends on the PWM frequency and PIC oscillator frequency as well. The PIC 16F876 datasheet gives more details about this. The upshot being that unless fine control is required (i.e. a possible 1024 different pulse widths are actually required) the extra overhead of maintaining the 2 LSB's in CCP1CON<5:4> is probably not warrented, and the PIC ROM saving could be more beneficial (i.e., the code fits in the PIC memory space or not!). Although, the pulse width value as calculated from equation 2 cannot "just" be put into CCPR1L and the 2 LSB's in CCP1CON<5:4> effectively ignored all together, as shown by the results in the following table.

duty cycle valuevalue as Binary 8bit/10bitvalue (1st 8 bits) + "00value >> 2 + "00decimal value (1st 8 bits) + "00decimal value >> 2 + "00value(1st 8 bits)+ "00" pulse width (sec)value (1st 8 bits) + "00" duty cycle %value >> 2 + "00" pulse width (sec)value >> 2+"00 duty cycle %
000000000000000000000000000000.000000%0.000000%
100000001000000010000000000400.000001%0.000000%
200000010000000100000000000800.000002%0.000000%
3000000110000001100000000001200.000002%0.000000%
4000001000000010000000001001640.000013%0.000001%
5000001010000010100000001002040.000014%0.000001%
6000001100000011000000001002440.000015%0.000001%
7000001110000011100000001002840.000016%0.000001%
8000010000000100000000010003280.000016%0.000002%
9000010010000100100000010003680.000017%0.000002%
10000010100000101000000010004080.000028%0.000002%
11000010110000101100000010004480.000029%0.000002%
5000110010001100100000110000200480.0000840%0.0000210%
100011001000110010000011001004001000.0001680%0.0000420%
125011111010111110100011111005001240.00020100%0.0000525%
200110010001100100000110010008002000.00032160%0.0000840%
2551111111111111111001111110010202520.00041204%0.0001050%
3000100101100001011000001001011001763000.0000735%0.0001260%
4000110010000100100000001100100005764000.00023115%0.0001680%
4940111101110111011100001111011009524920.00038190%0.0002098%
4960111110000111100000001111100009604960.00038192%0.0002099%
5000111110100111101000001111101009765000.00039195%0.00020100%

The results in the above table assume a 10MHz PIC oscillator, PR2=124 with timer2 prescaler = 4 (i.e., PWM period of 0.0002 sec, frequency 5kHz). Therefore, for a desired duty cycle of 50%, a value of 255 is needed within the effective 10bits of CCPR1L:CCP1CON<5:4>. As binary 255 = 11111111, and therefore requires "11" to be placed in CCP1CON<5:4> and the remaining "111111" in CCPR1L. If binary 255 is placed "just" within CCPR1L, this will be concatenated with "00"(or whatever else may be CCP1CON<5:4>), which in case of "00" would give an effective value of decimal 1020 (binary 1111111100), which gives a duty cycle greater than 100%, i.e. no PWM "square wave" just a constant "high" output.

However, if binary 255 is left shifted twice (the ">> 2" in the code) and then this result is placed "just" within CCPR1L. This in turn will be concatenated with "00" by the PIC giving an effective value of decimal 252 (binary 0011111100), which gives a duty cycle of 50% as desired (due to the decreased resolution). Therefore, left shifting twice the desired/calculated value of the pulse width (as per equation 2) and placing this result in CPR1L and ignoring the 2 bits in CCP1CON<5:4> "will work" just limiting the resolution of the pulse width available.

Downloads

Description Downloads
Pulse Width Modulation PIC registers calculations: Excel Spreadsheet Download

The PWM test circuit is the basic minimum required to enable a PIC microcontroller to operate after being programmed with a suitable .hex file. A schematic diagram is given in the following section.

Power Supply

A typical "wall-wart" power-supply is used (a surplus laptop charger in this case) in conjunction with a voltage regulator (LM7805) to provide the regulated 5V required by the PIC microcontroller.

Circuit Operation

The PIC microcontroller starts upon power-up and runs the pre-programmed (entered via the ISCP) hex code. The hex code, produced via CSS C and is described in the previous Background Section, uses the CCP module of the PIC 16F876 to produce various frequency pulse width modulated waveforms on the CCP1 pin (pin 13 of the microcontroller).

This circuit is simply to test the ability to produce the desired frequency and pulse-width duty-cycle waveform, no other practical use is made of the PWM output. The PWM output is measured (observed) using an oscilloscope.

Calibration

No calibration is required.

Note: Image loading can be slow depending on server load.

  • PIC PWM SchematicPIC PWM Schematic

    Silver Membership registration gives access to full resolution schematic diagrams.

    PIC PWM Schematic

This project did not require a PCB.

The construction was done using prototyping board. See the photographs and schematic diagram sections.

Qty Schematic Part-Reference Value Notes/Datasheet
Resistors
1R110k1/4W, 10% 
Capacitors
2C1,C222pFCeramic 
1C30.33uF 
1C40.1uF 
Integrated Circuits
1U1PIC16F876APIC microcontroller datasheet
1U27805Linear Voltage Regulator  datasheet
Diodes
1D21N-4004 
Miscellaeous
1J1CONN-H55-pin connector for ICSP
1SW1SW-SPDT 
1X110MHzCrystal Oscillator
Description Downloads
PIC PWM - Bill of Materials Text File Download

The following video shows the result of PWM code running on a PIC16F876A (see the "Background Section" for an explanation of the coding). The DMM response to voltage change is much slower than the PWM frequency, and so the DMM records what is effectively a changing analog voltage.

The code and circuit used to product the PWM output is described within the other sections on this page.

The "Background Section" also contains an explanation of how to calculate the necessary parameters to produce a desired frequency and duty cycle PWM. Some ramifications of these calculated values in terms of the hardware implementation within the PIC microcontroller, and how this relates to programming the values into the PIC IC are shown in various code snippets and tabulated calculated results.

The circuit is the basic minimum required to enable a PIC microcontroller to operate after being programmed with a suitable .hex file. So this circuit was only laid out on a breadboard.

Trouble Shooting

The "Background Section" above provides the necessary introduction to what pulse width modultion (PWM) is and how to implement on PIC microcontrollers. This information covers the use of the various special function registers (SRF) and how the CSS C Compiler helps with coding the implementation of PWM.

One potentially crucial concept to keep in mind (and possibly avoid confusion) is that the "duty cycle" on the PIC microcontroller is not entered into the appropriate register (i.e. memory location) as a percentage, BUT rather this value represents the number of oscillator cycles for the 'high' portion of the PWM square wave.

By calculating the correct number of oscillator cycles for the 'high' portion as a ratio of the total PWM period (again measured as oscillator cycles, i.e. as time) this gives the desired duty cycle as a percentage. The CSS C compiler built in functions (#USE PWM etc) help in this regard, as the duty cycle is indeed input as a percentage if using these functions.

The "Background Section" contains a video showing the PWM output from a PIC microcontroller displayed on an oscilloscope. The code and circuit used to product the PWM output is described within the other sections on this page.

Comments/Questions

No comments yet.

Add Comment/Question

Only Logged-In Members can add comments

"If we could sell our experiences for what they cost us, we'd all be millionaires".

Please donate any amount to help with hosting this web site.

If you subscribe (only $2/annum) you can view the site without advertisements and get emails abouts updates etc.

X

Sorry!

Only logged-in users with correct Membership Level can download.


Membership starts at only $2, so join now!