Using PWM in PIC16F877A

Table of Contents

Digital signals (0 or 1) and analog signals (range of values) are both used in electronics. Analog inputs can be converted to digital through an ADC. To control analog devices with a microcontroller, DACs are used but they’re costly and space-consuming. PWM (Pulse Width Modulation) is a cost-effective technique that imitates analog control using digital signals. It’s useful for controlling motors, lights, and actuators, though it’s not a true analog signal.

With electronic components, you can regulate the current they receive, even if your control is limited to toggling the power supply. By quickly alternating between powering on and off in a specific pattern, you can manage the delivered current. Adjusting the ratio of on-time to off-time increases or decreases the average power. This approach lets you create a spectrum of speeds (analog effect) for devices like motors and actuators, even though you’re essentially toggling between the binary states of on and off (digital). This technique is precisely what a PWM signal involves.

1. Pulse Width Modulation

In digital electronics, we define the “on-time” as logic high. To quantify the duration of this “on-time,” we employ the concept of a duty cycle. Essentially, the duty cycle denotes the proportion of time a digital signal spends in the “on” state within a given interval or period. This value is typically expressed as a percentage (%), with 100% being on all the time.

Imagine a signal with a peak voltage of 10V. If the full period of the signal is one second; and during this, it remains on for 0.5 seconds while being off for the other 0.5 seconds, it possesses a 50% duty cycle, yielding an average output voltage of 5V. If the on-time extends to 0.75 seconds and the off-time reduces to 0.25 seconds, the duty cycle becomes 75%, resulting in an output voltage of 7.5V. The illustration below, showcases signals with varying duty cycles and the corresponding average generated voltages. in PIC16F877A, changing the duty cycle is set by changing the specific registers; this will be explained in section 4.

Fig 1. Example of Pulse Width Modulation
Fig 1. Example of Pulse Width Modulation.

In addition to the duty cycle, the frequency is also important. The frequency is defined as the inverse of the period (in seconds). If the frequency is too low, lets say 1 Hz, then the LED turning off and on will be noticeable. Increasing the frequency to 1 kHz, and changing the duty cycle, will result in a proper dimming of the light without any side effects. Thus as long as the frequency is set sufficiently high enough for that application, it should be okay. For the PIC16F877A, the frequency of the PWM is generated by Timer2

2. PWM Initialization in PIC16F877A

PWM signals can be generated in almost all microcontrollers by using the CCP (Capture, Compare and PWM) module and a Timer module. The latter one is used to create a stable frequency in the range of 1 kHz up to 208 kHz (for the PIC16F877A). Due to hardware limitations, the maximum resolution of the duty cycle will go down with higher frequency. The PIC16F877A has a maximum resolution of 10 bits (1024 in decimals), and can keep this resolution up to a frequency of about 20 kHz, which is well above the application we will use it for.

To initialize the PWM in this microcontroller, we first have to look at all the registers that are required to set it up. We will go over them one by one:

Registers Description
T2CON
Timer2 control register, to set the rpescaler, postscaler and turning Timer2 on/off
PR2
Timer2 Period register, to set the max value of the counter
CCP1CON
This register controls the operations of CCP1
TRISC
This register is used to set PORTC as I/O
T2CON Register PIC16F877A

TOUTPS3:TOUTPS0: N/A for this application

TMR2ON: Timer2 On bit
1=Timer2 is on
0=Timer2 is off

T2CKPS1:T2CKPS0: Timer2 Clock Prescale Select bits
00 = Prescaler is 1
01 = Prescaler is 4
1x = Prescaler is 16

In this register, you set the Timer2 module on by setting the bit TMR2ON = 1. Additionally, you can change the frequency by changing the prescaler to any of the values shown above. As explained in the tutorial on Timer2, the Period Register (PR2) is used as the maximum value of the counter. This allows you to set the frequency of your PWM.

As an example, with a 20 MHz external crystal oscillator, the input frequency of the timer will be:

20 MHz / 4 = 5 MHz

and the corresponding time is 0.2 × 10-6 s. With a prescaler of 16, we can increase this further to

0.2 × 10-6 × 16 = 3.2 × 10-6 s.

If we want to create a frequency of 2.5 kHz, we need to create a timer that loops every 0.4 ms. We need to make sure that there are enough counts in the counter that add up to 0.4 ms.

0.4 × 10-3 / 125 = 3.2 × 10-6 s

From this calculation we can observe that we need 125 ticks in the counter. Hence we set PR2 to 124 (or 0x7C in hexadecimals), as the timer starts at 0.

CCP1CON Register PIC16F877A

CCPxX:CCPxY: N/A for initialization, later on this will be used however

CCPxM3:CCPxM0: CCPx Mode Select bits
0000 = Capture/Compare/PWM disabled (resets CCPx module)
0100 = Capture mode, every falling edge
0101 = Capture mode, every rising edge
0110 = Capture mode, every 4th rising edge
0111 = Capture mode, every 16th rising edge
1000 = Compare mode, set output on match (CCPxIF bit is set)
1001 = Compare mode, clear output on match (CCPxIF bit is set)
1010 = Compare mode, generate software interrupt on match (CCPxIF bit is set , CCPx pin is unaffected)
1011 = Compare mode, trigger special event (CCPxIF bit is set, CCPx pin is unaffected); CCP1 resets TMR1; CCP2 resets TMR1 and starts an A/D conversion (if A/D module is enabled)
11xx = PWM mode

To enable PWM mode in this register, CCPxM3:CCPxM0 should be set to 11xx. Hence CCP1CON = 0b00001100 (or 0x0C in hexadecimal).

3. Setting Duty Cycle in PIC16F877A

The duty cycle is a 10-bit value and is therefore split over 2 registers, namely CCP1CON (two LSBs) and CCPR1L (eight MSBs). Because it is a 10-bit timer, the duty cycle is 0% at a value of 0 and 100% at a value of 1023.

Registers Description
CCP1CON
This register contains the two LSB values of the duty cycle
CCPR1L
This register contains the 8 MSBs of the duty cycle
CCP1CON Register PIC16F877A

CCPxX:CCPxY: PWM Least Significant bits

CCPxM3:CCPxM0: N/A for the duty cycle

It is crucial to set this 10-bit number correctly to achieve the desired duty cycle. To elaborate on this concept, i kindly refer to the code snippet below for a clear mapping of the duty cycle values.

				
					// This function converts percentage into a 10-bit value, and sets it in the right registers
void set_pwm_duty_cycle(unsigned int percent) {
    // Calculate the equivalent 10-bit value from the percentage input
    unsigned int duty_cycle_value = (percent * 1023) / 100;

    // Separate the 10-bit value into upper 8 bits (CCPR1L) and lower 2 bits (CCP1X, CCP1Y) 
    unsigned char ccpr1l_value = duty_cycle_value >> 2;   // Shifting the LSBs out, only leaving MSBs behind
    unsigned char ccp1xy_value = duty_cycle_value & 0b11; // ANDing the entire bit stream with 0b11 to get the 2 LSBs

    // Set the CCP1X and CCP1Y bits in the CCPxCON register
    CCP1X = (ccp1xy_value >> 1) & 0b1;
    CCP1Y = ccp1xy_value & 0b1;

    // Set the CCPR1L register with the upper 8 bits
    CCPR1L = ccpr1l_value;
}
				
			

4.1 Programming Code for MPLAB

In this code we will create a loop that goes through the duty cycle from 0% to 100% in one second and back to 0%.

				
					#include <pic16f877a.h>
#include <xc.h>

// Define the frequency of the external crystal oscillator
#define XTAL_FREQ 20000000  // 20MHz external crystal oscillator frequency

// Configuration bits
#pragma config FOSC = HS    // High-Speed Crystal oscillator
#pragma config WDTE = OFF   // Watchdog Timer disabled
#pragma config PWRTE = OFF  // Power-up Timer disabled
#pragma config BOREN = OFF  // Brown-out Reset disabled
#pragma config LVP = OFF    // Low-Voltage Programming disabled
#pragma config CPD = OFF    // Data memory code protection off
#pragma config WRT = OFF    // Flash Program Memory Write protection off
#pragma config CP = OFF     // Flash Program Memory Code protection off

// Function to set the PWM duty cycle based on percentage
void set_pwm_duty_cycle(unsigned int percent) {
    // Calculate the equivalent 10-bit value from the percentage input
    unsigned int duty_cycle_value = (percent * 1023) / 100;

    // Separate the 10-bit value into upper 8 bits (CCPR1L) and lower 2 bits (CCP1X, CCP1Y) 
    unsigned char ccpr1l_value = duty_cycle_value >> 2;   // Shift to extract upper 8 bits
    unsigned char ccp1xy_value = duty_cycle_value & 0b11; // Extract lower 2 bits

    // Set the CCP1X and CCP1Y bits in the CCPxCON register
    CCP1X = (ccp1xy_value >> 1) & 0b1;
    CCP1Y = ccp1xy_value & 0b1;

    // Set the CCPR1L register with the upper 8 bits
    CCPR1L = ccpr1l_value;
}

void main()
{
    // Initializing PWM in PIC16F877A
    TRISCbits.TRISC2 = 0; // Configure RC2/CCP1 pin as output
    CCP1CON = 0x0C;       // Set PWM mode on
    PR2 = 0xC2;           // Set timer frequency to 2.5 kHz - by counting up to 194 times
    T2CON = 0x06;         // Prescaler settings 1:16 and turning on Timer2

    while (1)
    {
        // Increase duty cycle from 0 to 100%
        for (int i = 0; i <= 100; i++) {
            set_pwm_duty_cycle(i);
            __delay_ms(10); 
        }

        // Decrease duty cycle from 100 to 0%
        for (int i = 99; i >= 0; i--) {
            set_pwm_duty_cycle(i);
            __delay_ms(10); 
        }
    }
}
				
			
Share
Tweet
Share
Pin
Email
5 2 votes
Article Rating
Subscribe
Notify of
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments