Generating PWM with PIC Microcontroller – MPLAB XC8

Generating PWM with PIC Microcontroller – MPLAB XC8

Pulse Width Modulation (PWM) is the one of the simple and most commonly used technique to produce analog voltages from digital signals. It has a wide variety of applications such as Digital to Analog Converter (DAC), DC Motor Speed Control, Sine Wave Inverters, Brightness control etc.

PWM signals are ON – OFF signals (HIGH or LOW) (hence the name Pulse) whose HIGH or ON duration is changed (hence Width Modulation) in accordance with our requirements. The fraction of time period for which the signal is ON to the total time period is termed as Duty Cycle.

PWM waves can be easily generated using CCP modules available with most of the PIC Microcontrollers. CCP stands for Capture / Compare / PWM, which means that it can be used for Capture or Compare or PWM operations. To program this module with MPLAB XC8 compiler we need to learn its working. Here for demonstration we are using PIC 16F877A microcontroller.

CCP – Capture / Compare / PWM Module

Microchip’s PIC 16F877A microcontroller has two CCP modules, named as CCP1 and CCP2. Each CCP module comprises of two 8 bit registers which can be operate as :

  • 16 bit Capture Register
  • 16 bit Compare Register
  • PWM Master / Slave Duty Cycle register

This tutorial deals only with PWM operation of CCP module. Using this we can generate PWM output having resolution up to 10 bit. Output of CCP modules are multiplexed with RC1 & RC2 of PORTC, hence TRIS<1> and TRIS<2> must be cleared to make these pins output.


The simplified block diagram of PWM mode of CCP module is shown below.

PWM Simplified Block Diagram - PIC 16F877A

PWM Simplified Block Diagram – PIC 16F877A

Timer 2 module provides the time base for the PWM operation of both CCP modules. The value of the TMR2 register increases from zero to maximum value as per the timer 2 input clock. The input clock is determined by the microcontroller clock frequency (Fosc) and timer 2 prescaler value.

Timer 2 Block Diagram - PIC 16F877A

Timer 2 Block Diagram – PIC 16F877A

  • Prescaler Input Clock : Fosc / 4
  • Timer 2 Input Clock : (Fosc / 4) / Prescaler Value = Fosc / (4*Prescaler)

Time period of the generated PWM waves are determined by the value of PR2 Register. The comparator compares the values of PR2 and TMR2 registers. When these values become equal, the output pulse from the comparator sets the PWM output to HIGH. It also resets the Timer 2 value to zero.

The PWM duty cycle is determined by the value in the CCPR1L and CCP1CON<5:4> registers, which can be written any time. This value is stored to CCPR1H and a 2 bit internal latch when there is a match between PR2 and Timer 2. This avoids the possibility of glitches in the PWM output due to changing of duty cycle.

PWM Output

PWM Output

The PWM output become LOW when there is a match between Timer 2 value and Duty Cycle (CCPR1L and CCP1CON<5:4>).

Note 1 : The value of Duty Cycle should be less than Time Period (PR2) for the proper generation of PWM signals.

Note 2 : It is not possible to use different PWM frequencies for both CCP modules, because they use Timer 2 for their operation.

PWM Resolution

PWM Resolution of CCP modules can be calculated using the following equation.

PWM Resolution


  1. Set the PWM Period by writing to PR2 register. The relation between PR2 value and time period is shown below.PWM Period - PIC Microcontroller
  2. Then set the PWM Duty Cycle by writing to CCPR1L and CCP1CON<5:4> registers. The relation between register value and duty cycle is shown below.PWM Duty Cycle
  3. Configure the CCP pins as Output by writing to TRIS<2> and TRIS<1> bits.
  4. Set the Timer 2 (TMR2) prescale value and enable it by writing to T2CON register.
  5. Finally, configure the CCP module for PWM operation by writing to CCP control registers.

Circuit Diagram

Generating PWM with PIC Microcontroller - Circuit Diagram

Generating PWM with PIC Microcontroller – Circuit Diagram

This circuit can be used to demonstrate the working of PWM. An LED is connected to the output of each CCP module through a series resistor to limit the current. Push Button Switches are used to adjust the PWM duty of both channels. We can see that the light intensity of LED increases as the PWM duty increases.

MPLAB XC8 Program

#define _XTAL_FREQ 20000000
#define TMR2PRESCALE 4

#include <xc.h>

#pragma config FOSC = HS   // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF  // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = ON  // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF   // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF   // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF   // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF   // Flash Program Memory Code Protection bit (Code protection off)

long freq;

int PWM_Max_Duty()

PWM1_Init(long fre)
  PR2 = (_XTAL_FREQ/(fre*4*TMR2PRESCALE)) - 1;
  freq = fre;

PWM2_Init(long fre)
  PR2 = (_XTAL_FREQ/(fre*4*TMR2PRESCALE)) - 1;
  freq = fre;

PWM1_Duty(unsigned int duty)
    duty = ((float)duty/1023)*PWM_Max_Duty();
    CCP1X = duty & 2;
    CCP1Y = duty & 1;
    CCPR1L = duty>>2;

PWM2_Duty(unsigned int duty)
    duty = ((float)duty/1023)*PWM_Max_Duty();
    CCP2X = duty & 2;
    CCP2Y = duty & 1;
    CCPR2L = duty>>2;

  CCP1M3 = 1;
  CCP1M2 = 1;
  #if TMR2PRESCALAR == 1
    T2CKPS0 = 0;
    T2CKPS1 = 0;
  #elif TMR2PRESCALAR == 4
    T2CKPS0 = 1;
    T2CKPS1 = 0;
  #elif TMR2PRESCALAR == 16
    T2CKPS0 = 1;
    T2CKPS1 = 1;
  TMR2ON = 1;
  TRISC2 = 0;

  CCP1M3 = 0;
  CCP1M2 = 0;

  CCP2M3 = 1;
  CCP2M2 = 1;
  #if TMR2PRESCALE == 1
    T2CKPS0 = 0;
    T2CKPS1 = 0;
  #elif TMR2PRESCALE == 4
    T2CKPS0 = 1;
    T2CKPS1 = 0;
  #elif TMR2PRESCALE == 16
    T2CKPS0 = 1;
    T2CKPS1 = 1;
    TMR2ON = 1;
    TRISC1 = 0;

  CCP2M3 = 0;
  CCP2M2 = 0;

void main()
  unsigned int i=0,j=0;
  TRISD = 0xFF;
  TRISB = 0;
    if(RD0 == 0 && i<1000)
    if(RD1 == 0 && i>0)
    if(RD2 == 0 && j<1000)
    if(RD3 == 0 && j>0)


  • PWM1_Init(frequency) & PWM2_Init(frequency) will initialize the CCP1 and CCP2 modules respectively in the PWM mode with the specified frequency. You can’t have different frequencies for CCP modules as both of them uses Timer 2 for their operation.
  • PWM1_Duty(duty) & PWM2_Duty(duty) will set the duty cycle of the generated PWM signals. The parameter duty can be any value ranging from 0 to 1023 corresponding to 0% to 100% duty cycle.
  • PWM1_Start() & PWM2_Start() are used to initialize / start the PWM signal generation.
  • PWM1_Stop() & PWM2_Stop() are used to stop the PWM signal generation.

Download Here

You can download MPLAB XC8 project files here.


Share this post

Comments (32)

  • ali

    hi im a beginner in pic . and i want to send data with spi between tw0 pic16f877a ,please who can help me?

    May 29, 2015 at 4:00 pm
  • manjula

    void main()
    CMCON = 0x07; // To turn off comparators
    ADCON1 =0x06; //Turne off adc
    TRISB = 0x00; // Sets all pins in PORTB as output
    PORTB = 0b00000001; // Set RB0 to high 00000001
    TRISA = 0x00; // Sets all pins in PORTB as output
    PORTA = 0b00000000; // Set RB0 to high 00000001

    do // To set infinite loop
    Delay_ms(100); // 300 mili seconds delay
    PORTA = PORTA<= 0b10000000) //To reset to 00000001
    { //when the count becomes 10000000
    PORTA = 0b00000000
    PORTB = 0b00000001
    if(PORTB >= 0b00000001) //If RB7 =1
    PORTB = PORTB<= 0b10000000) //To reset to 00000001
    Delay_ms(100); //
    PORTB =0b00000000; //RB low
    PORTA =0b00000001;
    }while(1); // To set infinite loop

    plz edit and send to correct C file

    July 5, 2015 at 2:57 pm
  • Ligo George

    Please use our forums ( ) for asking doubts outside the scope of above tutorial.

    July 18, 2015 at 11:04 am
  • Tony

    Hello, please i need you to explain the fomula PR2 = (_XTAL_FREQ/(fre*4*TMR2PRESCALE)) – 1; in the function PWM1_Init( long fre). From the PIC16f877 data sheet the formula for the pwm width= (PR2+1)*4*XTAL FREQ*TMR2PRESCALE. If you solve for PR2 what you get is different from the formula PR2 = (_XTAL_FREQ/(fre*4*TMR2PRESCALE)) – 1;
    Please can you explain why?

    August 21, 2015 at 6:30 pm
  • Ligo George

    Datasheet formula is PW = (PR2 + 1)*4*Tosc*TMR2PS

    October 3, 2015 at 2:58 pm
  • Rahim Bestknight

    there is probleme with the PWM2 it don’t work

    February 1, 2016 at 11:44 pm
  • Ligo George

    It is 100% working fine. I checked in both proteus and hardware. I took a video also and will upload soon.

    February 2, 2016 at 5:12 pm
  • Aymen Fezai

    is there any way to get 250Hz pulse from PIC?

    March 8, 2016 at 2:02 am
  • Muhammad Ilyas

    Ligo George..can u upload a video of it..
    ur help will highly be appreciated.thanks

    March 9, 2016 at 11:00 am
  • Muhammad Ilyas

    there is an error in TMR2PRESCAL…it is highlighted in the mplab x ide .
    what could be the problem?

    March 9, 2016 at 11:06 am
  • Ligo George

    Yes, you can generate it using CCP module but you need to use a lower clock frequency for pic. I hope 4MHz will work.

    March 19, 2016 at 11:00 am
  • Ligo George

    Uploaded, please check it.

    March 19, 2016 at 11:07 am
  • Ligo George

    Don’t you defined the TMR2PRESCALE value using #define ?

    March 19, 2016 at 11:08 am
  • Omotoso Omoniyi

    Nice Tutorial George, please how do I make this work for a PIC 16F690 micro controller with just one CCP module. What will be the difference? Thanks

    April 11, 2016 at 2:12 am
  • Ligo George

    Just compare both datasheets and make necessary changes.

    April 15, 2016 at 8:22 am
  • Rahul Singh

    Hi. Is there any way to save the last state to eeprom when we switch off the power. Both pwm signals go to zero when power fails. thanks

    April 27, 2016 at 5:39 pm
  • Ligo George

    Yes, it is there. You can save the status to the PIC internal eeprom. Sorry we don’t have any XC8 tutorials for it now.
    You can make use of following tutorial now.

    June 2, 2016 at 6:22 pm
  • Manasa Ramprasad

    CCP1M3 = 1;
    CCP1M2 = 1;

    July 28, 2016 at 12:15 pm
  • Fabien

    Hi, it’s a good website. Tutorials are well explained, very usefull. What do you think to set the contrast of an LCD screen (with HD44780 controller) by using the PWM module of a PIC, and push buttons to change the PWM duty cycle ?

    August 3, 2016 at 7:50 pm
  • Ligo George

    Please see the definition of CCPxCON register in the datasheet. You can see that we need to set those bit to make CCP module to work as PWM generator.

    August 27, 2016 at 1:55 pm
  • Ligo George

    Thanks for the feedback.

    Yes, we may do like that. But I think it is not a good option for HD44780 LCDs. So pic microcontrollers have built in LCD drivers. With that we can easily interface LCD glass. In that case it is really useful.

    August 27, 2016 at 4:17 pm
  • Ligo George

    Thanks for the feedback.

    Yes you may do like that. But I don’t prefer it for LCD controller by HD44780. Some pic microcontroller have inbuilt LCD driver module with which we can drive LCD glass. In that case we can easily change the contrast by changing driving voltage.

    August 27, 2016 at 9:21 pm
  • Shonda Jackson

    Do you have a reference or link to any info about code that I would like to change the PWM freq dynamically – i am using mikroc w 8MHz crystal on a PIC18F26K22 chip and PWM works fine at the higher frequenices, but it does not allow me to set frequencies less than 500 Hz.

    For example, utilizing PWM1_Init(487) – I try to compile and I get an error “Argument is out of range”

    I do have success at lower freq if i try a Short Delay Routine (SDR) such as :
    while(RB0_bit == 1)
    RC2_bit = 1;
    RC2_bit = 0;

    My problem can be summed up this way
    1. I am able to run the PWM from 500 Hz to 1000 Hz fine by itself.
    2. I am able to run the SDR from 5 Hz to 500 Hz fine by itself
    3. When I try to combine PWM and SDR – the PWM range (500 – 1000 – the PWM works fine but it seems that even though use the command PWM1_STOP() – it seems that the PWM process does not release control of the pin so that I can manipulate the signal utilizing the SDR for frequencies less than 500 Hz.

    I have tried USING ONLY using the SDR for the entire range of 5 Hz to 1000 Hz utilizing a switch statement, but I get a compilation error (There is not enough ROM space) as well when I try to compile.

    switch (freq)
    case 184: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(1019); RC2_bit = 0; Delay_us(1019); } break;
    case 183: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(1024); RC2_bit = 0; Delay_us(1025); } break;
    case 182: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(1030); RC2_bit = 0; Delay_us(1030); } break;
    case 181: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(1035); RC2_bit = 0; Delay_us(1036); } break;
    case 180: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(1041); RC2_bit = 0; Delay_us(1042); } break;
    // etc …
    // etc …
    case 10: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(18750); RC2_bit = 0; Delay_us(18750); } break;
    case 9: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(20833); RC2_bit = 0; Delay_us(20833); } break;
    case 8: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(23437); RC2_bit = 0; Delay_us(23438); } break;
    case 7: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(26785); RC2_bit = 0; Delay_us(26786); } break;
    case 6: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(31250); RC2_bit = 0; Delay_us(31250); } break;
    case 5: while(RB0_bit == 1) { RC2_bit = 1; Delay_us(37500); RC2_bit = 0; Delay_us(37500); } break;

    October 30, 2016 at 1:58 am
  • Ligo George

    PWM module is not intended for LOW frequencies. You may try coding with normail GPIO pins for that.

    November 27, 2016 at 2:28 pm
  • Jogesh

    Hi Ligo, I follow all your PIC projects using XC8 and have simulated many of them. This PWM project has code “#if”, “#elif” and “#endif”. Unfortunately my MPLAPXIDE and XC8 are giving errors for these instructions. Please help in resolving this. Many Thanks. Jogesh

    January 4, 2017 at 10:09 am
  • Pier

    Great Tutorial, thanks for sharing!

    February 14, 2017 at 3:51 pm
  • Freddy

    Hi Ligo, could the push button switches be replaced with a potentiometer (one per PWM), i am having trouble with what the circuit/code adjustments would be to do this.

    March 18, 2017 at 10:17 am
  • Aravind NA

    Hi,does it possible to produce 100hz-200hz by using above method?nice tutorial….

    April 12, 2017 at 12:52 am
  • Rolando Aguilera

    can u explain this part of the code please? Where did you get the logic of AND operator and then right shifting?
    PWM1_Duty(unsigned int duty)

    June 26, 2017 at 2:27 pm
  • Clement Victor

    An excellent tutorial on PWM
    Beautifully done

    October 21, 2017 at 3:29 pm
  • Emanu

    Very interesting, i like to see this program in c18 toolsuite, please help me!

    January 9, 2018 at 11:51 pm
  • Yousif

    Very good tutorial mate , thanks alot , i just have a question , is it possible to generate an inversed PWM signal ?

    April 9, 2019 at 8:18 am

Leave a Reply

Your email address will not be published. Required fields are marked *