10-Bit Pulse Width Modulation (PWM) on PIC Devices

Last modified by Microchip on 2023/11/10 11:10

10-Bit Pulse Width Modulation (PWM) can be a stand-alone peripheral on some newer PIC® MCU devices, incorporated into the Capture Compare PWM (CCP) peripheral, or incorporated into the Enhanced Capture Compare PWM (ECCP) peripheral. In all cases, they operate in a very similar way.

The 10-bit PWM is controlled by Timer2 within the PIC MCU. Timer2 is incremented, through a 1:1 prescaler, from the instruction clock, which is the output of the system oscillator run through a 1:4 postscaler (FOSC/4). Timer2 is actually an 8-bit timer, so in order to get 10-bits of resolution, Timer2 is combined with the two least significant bits of the FOSC/4 postscaler.

To slow Timer2 the prescaler can have a setting of 1:4, 1:16, and 1:64 (only on some devices). When the prescaler is used beyond 1:1, then the lower two bits of the 10-bit Timer2 come from the two least significant bits of the prescaler.

​On some devices with multiple PWM peripherals, there may be additional timers such as Timer4 and Timer6. Essentially, these are additional Timer2 peripherals that allow each PWM module to be set to a unique timebase.


The high time of the PWM signal is controlled by a 10-bit value that is stored in two 8-bit registers; PWMxDCH and PWMxDCL on the stand-alone PWM or CCPRxL and CCPxCON on the CCP and ECCP peripherals. The PWMxDCH or CCPRxL register contains the upper 8 bits and the PWMxDCL or CCPxCON register contains the lower two bits.

When the 10-bit value in these two registers matches the value in the 10-bit Timer2 concatenation, the PWM output will switch from a high setting to a low setting. This creates the pulse-width of the signal and determines the duty cycle.

Timer2 continues to run after the output switches state until its upper 8-bit value matches a second 8-bit value stored in the Period (PR2) register. When these match, the PWM output changes state from low to high and Timer2 is reset to zero. The value stored in PR2 determines the period of the PWM signal and by default the frequency of the signal.

Timer2 Block DiagramPWM Signal showing Period and Pulsewidth

Back to Top

Calculating the PWM Settings

There are two formulas that are used to:
1) Calculate the PWMxDCH:PWMxDCL or CCPRxL:CCPxCON value for the desired signal pulse width.
2) Calculate the PR2 value for the desired signal period.

Pulsewidth Calculation

Pulsewidth Calculation

Period Calculation

Period Calculation


Note: This example is set up for a stand-alone PWM but can equally apply to a CCP or ECCP peripheral setup in PWM mode.

Back to Top


Typically, the desired frequency is already known so the period formula can be reworked to solve for the PR2 value that meets the frequency requirement.

(1) PR2=[(Fosc)/(4∗TMR2Prescale∗PWMFrequency)]−1

For example, if we use the internal oscillator set to Fosc = 4 Mhz along with a TMR2 prescaler of 1:16 and want a PWM with a frequency of 500 Hz; then PR2 is calculated to be 124:

(2) PR2=[4Mhz/(4∗16∗500Hz)]–1=124

If the number ever calculates to a value larger than 255, then you would have to increase the prescaler to a larger ratio or lower the oscillator speed.

Back to Top

Duty Cycle

The Duty Cycle desired is also typically a known value, so once the frequency is set via the PR2 value then the pulse width calculation can be reworked to solve for the PWMxDCH:PWMxDCL value with the formula below:

(3) PWMxDCH:PWMxDCL=4∗(PR2+1)∗DutyCycleRatio

Since PR2 was already calculated as 124, and assuming a 50% duty cycle, then the answer below is calculated:

(4) PWMxDCH:PWMxDCL=4∗(124+1)∗0.5=250

250 in binary is 0011111010, which gets broken up between the two registers to:

(5) PWMxDCH=00111110


(6) PWMxDCL=10

Back to Top

MPLAB Code Configurator PWM Example

MPLAB® Code Configurator (MCC) makes setting up a 10-bit PWM peripheral easy. The steps include setting up the I/O, Timer2, and PWM module to make it run. MCC will automatically generate the code to load the proper registers and initialize the proper values to produce the desired PWM signal.

The best way to show how this is done is through a simple example. We will configure a PIC16F1825 Capture/Compare/PWM peripheral to create a PWM signal at 500 Hz, 50% duty cycle using a 4 MHz system clock and 1:16 prescaler.

Back to Top


The first step after launching MCC within MPLAB® X is to select the peripherals we will use and set up the PWM. As seen below, the three resources required are the System, TMR2::Timer, and the CCP3:PWM modules with the MCC list of options.

MCC list of options

Back to Top

System Setup

The system is where the oscillator speed is selected and any changes to the configuration settings you may need. Select the 4 MHz internal oscillator as shown in the picture below.

Select the 4 MHz internal oscillator

Back to Top

Timer2 Setup

Timer2 uses the oscillator selected in the System section to adjust the Timer2 period. Enter the time of "2.0 ms" for the period to yield a 500 Hz frequency. Select the prescaler as 1:16 from the drop-down menu. Check the Start Timer After Initialization box. This will start the timer running and also the PWM signal after the PIC16F1825 finishes initializing all the peripherals.

Start Timer After Initialization box

Back to Top

CCP3:PWM Setup

By selecting the CCP3:PWM, MCC automatically selects the I/O pin RA2 in the I/O selection window. The RA2 pin actually shows up with the label CCP3 in green to show that the CCP3 peripheral now controls the I/O pin.

MCC Pin Manager

The CCP3:PWM setup screen is where the Duty Cycle is selected. Enter "50" for 50%.

PWM period and frequency are displayed in this window as well based on the Timer2 selection window.

Timer2 selection

Back to Top

Generate Code

When all the setup screens are complete, click the MCC Generate Code button Generate Code Button and MCC produces the software files for the project. The MCC will produce a main.c file that contains a System_Initialize function as its only component.

main.c file that contains a System_Initialize function

The System_Initialize function is placed in a file named mcc.c.
System_Initialize calls four functions:


List of Functions CalledThe OSCILLATOR_Initialize function takes the Oscillator Settings selected and sets up the proper registers for the 4 MHz internal oscillator.

Register setup for the 4 MHz internal oscillatorThe PIN_MANAGER_Initialize function sets the registers for the I/O pins.

register initialization for the I/O pinsThe TMR2_Initialize function sets the registers for the Timer2 settings selected including the prescaler and PR2 value.

Timer2 settingsThe PWM3_Initialize function selects the settings for the 50% duty cycle value. Notice the CCP3RL register is loaded with the proper value to create the proper high time of the 50% duty cycle.

PWM3_Initialize functionThe code is then compiled within MPLABX IDE environment and programmed into the PIC16F1825. The device will start operating as soon as it's powered up. Timer2 will start running immediately after the initialization phase of the code. The results are shown on the oscilloscope screen capture below. The screen capture shows, in the measurement section, a period of 2 milliseconds and frequency of 500 Hz as we expected. Each pulse is an equal 1 milliseconds off the center of the signal for a perfect 50% duty cycle.

Scopeplot of PWM

For more information on Pulse Width Modulation (PWM) options, visit the "Pulse Width Modulation (PWM)" page

Back to Top