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.

Generating PWM with PIC Microcontroller – MPLAB XC8CCP – 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.

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.

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

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.

Generating PWM with PIC Microcontroller – MPLAB XC8 SchematicPWM Resolution

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


  1. Set the PWM Period by writing to PR2 register. The relation between PR2 value and time period is shown below.
  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.
  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.


For more detail: Generating PWM with PIC Microcontroller – MPLAB XC8

Current Project / Post can also be found using:

  • pwm generation using pic16f877a pdf
  • pic16f877a pwm using mplab xc8
  • XC8 setup CCP pulse
  • pic pwm

Leave a Comment

= 5 + 1

Read previous post:
External interrupt For CSS Compiler

#include <16F877.h> #device adc=8 #fuses XT,NOWDT,NOPROTECT,NOLVP #use delay(clock=4000000) #INT_EXT void EXT_ISR(void){ output_toggle(PIN_B7); delay_us(300); } void Init_MCU(void){ enable_interrupts(GLOBAL); enable_interrupts(INT_EXT); ext_int_edge(H_TO_L); set_tris_B(0x01);...

Scroll to top