## Simple PWM example for the PIC18F4620

The PIC18F4620 includes a hardware pulse width modulation feature which is really useful for generating periodic pulse sequences with different frequencies and duty cycles. Applications include motor speed control, servo control, and varying the brightness of an LED. This simple example program generates a 1kHz PWM (pulse width modulation) signal on pin 17 (RC2 / CCP1). The program is written in C for Microchip’s XC8 C compiler.

NB The PIC18F4620 provides 10-bit resolution in setting the PWM pulse width. However, to keep things simple, I’m disregarding the 2 least significant bits, which reside in a different register to the 8 most significant bits. Unless you really need the extra resolution, I recommend ignoring the 2 least significant bits and treating both the PWM period and pulse width as 8-bit values. Because I’m making this simplification, the calculations I present below are a bit simpler than those described in the PIC18F4620 datasheet.

### Setting the PWM period and duty cycle

In this example, the clock oscillator frequency of the 18F4620 is left at the default value of Fosc = 1MHz. The oscillator period is therefore Tosc = 1us.

The PIC18F4620 performs one machine code instruction every four clock cycles, so the instruction cycle in this example is Tcy = 4us. This is an important figure because Tcy is really the fundamental unit of time when measuring time in PIC programs.

The period of the PWM waveform is determined by the value written to a special function register called PR2 (short for Period Register for Timer 2). The formula for calculating PWM period is:

PWM period = (PR2+1) * Timer 2 prescaler value * Tcy

In this example, PR2 = 249, the Timer 2 prescaler = 1 and Tcy = 4us. Therefore,

PWM period = 250 * 1 * 4us = 1ms

The pulse width of the PWM waveform is determined by the value written to a special function register called CCPR1L (short for Capture/Compare/PWM channel 1 Register, low byte). The formula for calculating PWM pulse width is:

PWM pulse width = CCPR1L * Timer 2 presacler value * Tcy

Initially, CCPR1L = 125, the Timer 2 prescaler = 1 and Tcy = 4us. Initially therefore,

PWM period = 125 * 1 * 4us = 0.5ms

The values of PR2 and CCPR1L can be changed at any time, which will change the period or pulse width of the waveform. In this example, the period is constant at 1ms, giving a frequency of 1kHz. However, the value of CCPR1L changes every half a seconds, causing the duty cycle of the waveform to cycle through the values 50%, 10%, 0% repeatedly.

### C code and build script

This is the full C code for the XC8 compiler.

```//
// PIC18F4620 1kHz PWM example program
// Written by Ted Burke (http://batchloaf.com)
// Last updated 4-4-2013
//
// To compile with XC8:
//     xc8 --chip=18F4620 main.c
//

#include <xc.h>

#pragma config OSC=INTIO67,MCLRE=OFF,WDT=OFF,LVP=OFF,BOREN=OFF

int main(void)
{
// Set up PWM (see section 15.4 of the PIC18F4620 datasheet)
CCP1CON = 0b00001100;   // Enable PWM on CCP1
TRISC = 0b11111001;     // Make pin 17 (RC1/CCP2) an output
T2CON = 0b00000100;     // Enable TMR2 with prescaler = 1
PR2 = 249;   // PWM period = (PR2+1) * prescaler * Tcy = 1ms
CCPR1L = 25; // pulse width = CCPR1L * prescaler * Tcy = 100us

while(1)
{
// 50% duty cycle for 500ms
CCPR1L = 125;
_delay(125000);

// 10% duty cycle for 500ms
CCPR1L = 25;
_delay(125000);

// 0% duty cycle for 500ms
CCPR1L = 0;
_delay(125000);
}
}
```

I don’t use MPLAB; I just write my C code in a text editor, compile it with XC8 in a command window, then use the PICkit 2 application to transfer the hex file to the PIC. The build script I use (which contains just one command) is shown below. I save the C code as “main.c” and the build script in the same folder as “build.bat”.

```xc8 --chip=18F4620 main.c
```

Here’s how it looked when I compiled the program on my laptop:

### Measuring the waveform period and pulse width

I used the PICkit2 Logic Analyzer to take the following snapshot of the waveform when the duty cycle was 50% (when CCPR1L = 125). The signal was connected to channel 3 of the logic analyzer (pin 6 of the PICkit 2):

Here, the logic analyzer cursors are measuring the period of the waveform:

Here, the logic analyzer cursors are measuring the pulse width of the waveform:

Finally, here’s the waveform when the duty cycle is set to 10% (i.e. when CCPR1L = 25):

This entry was posted in Uncategorized. Bookmark the permalink.

### 21 Responses to Simple PWM example for the PIC18F4620

1. Geoffry says:

I’m currently doing a similar project with a pic18f14k50 on the new XC8 the only difference is I am using a dual latch relay as two different inputs into the pic, one latch(swithch) turns the relay 90 degrees when triggered, the other latch turns it back to 0 when triggered. How different would my program be from yours?

• batchloaf says:

Hi Geoffry,

Sorry, I don’t fully understand your question. You say the relay provides input to the PIC. What is the PWM controlling?

Maybe you mean that you are using two switches as inputs to the PIC and that the PIC is controlling a servo motor that turns back and forth through a 90 degree angle? I’m imagining that the servo should rotate to one position when the first button is pressed, and rotate to the other position when the second button is pressed. If so, I can modify the example above to show you how it would look.

Ted

• Geoffry says:

Yes, that what I mean. Sorry, that I wasn’t more clear. I’m using a relay as an input to switch back and forth instead of a push button. I want the relay to go to 90 degrees when the relay is switched on and back to 0 degrees when the relay is switched off.

2. Geoffry says:

I know also my pic has less pins and but uses the same type of registers, so there would be some changes to the code.

• batchloaf says:

Hi Geoffry,

I’m afraid I’m still a bit confused! Do you mean that you want a servo to go back and forth between 0 and 90 degrees as the relay switches on and off? If so, perhaps the following code would do it…?

```//
// PIC18F14K50 50Hz PWM example program
// Written by Ted Burke (http://batchloaf.com)
// Last updated 22-4-2013
//
// To compile with XC8:
//     xc8 --chip=18F14K50 main.c
//

#include

#pragma config FOSC=IRC,MCLRE=OFF,WDTEN=0,LVP=OFF,BOREN=OFF

int main(void)
{
// Set clock frequency to 500kHz, therefore Tcy = 8us
OSCCONbits.IRCF = 0b010;

// Set up PWM
CCP1CON = 0b00001100;   // Enable PWM on CCP1
TRISC = 0b11011111;		// Make CCP1 an output
T2CON = 0b00000110;     // Enable TMR2 with prescaler = 16
PR2 = 156;	// PWM period = (PR2+1) * prescaler * Tcy = 19.968ms
CCPR1L = 8;	// pulse width = CCPR1L * prescaler * Tcy = 1.024ms

while(1)
{
if (PORTCbits.RC4) CCPR1L = 12;	// 1.536ms pulses, i.e. 90 degrees
else CCPR1L = 8;				// 1.024ms pulses, i.e. 0 degrees
}
}
```

The PWM signal to control the servo is outputted on CCP1 (pin 5). The PWM period is 20ms, which is correct for most of the servos I use. The pulse width is either 1ms or 1.5ms (approx) depending on whether RC4 (pin 6) is high or low. That’s the pin you should connect your relay to. I estimated the pulse widths for 0 and 90 degrees based on the typical properties of servos I’ve used, but you may need to adjust the values for CCP1RL (I used 8 and 12).

I don’t have a PIC18F14K50 to try this program with, but I’ve run it in the MPLAB simulator and it seems to work. Let me know if that’s what you need or not.

Ted

• Geoffry says:

I tested on my pic and a servo and everything works great when the switch is turned on. When I turn it off however, the servo will not go back to 0. Do I need to try a different values for CCP1RL?

• batchloaf says:

Hi Geoffry,

It’s possible that you’re just missing a pull-up or pull-down resistor on RC4 (pin 6), so that the input voltage is not changing reliably when you switch the relay. This is by far the most common reason that students in my classes fail to get switch inputs working reliably.

Try disconnecting the relay from the input. Instead, use a piece of wire to connect pin RC4 to Vdd (pin 1) and Vss (pin 20) one after another (definitely not at the same time!). Connecting to Vdd will make the digital input (RC4) high. Connecting to Vss will make the digital input low. You should see the servo moving back and forth.

Exactly what model of servo and relay are you using by the way?

Ted

3. Geoffry says:

I’m using a S05NF Hobby servo, it only moves in 180 degree motion. Also, what type of pull down or pull up resistor should I use?

• batchloaf says:

Hi Geoffry,

Did you try connecting RC4 to Vss and Vdd one after another as I suggested?

For a pull-up or pull-down resistor on a switch input, I would normally use a 10kOhm resistor. Let’s assume you were going to use a pull-down… The basic idea is that the pull-down resistor is connected between RC4 and ground (Vss = 0V). Under normal circumstances, this causes the pin voltage (RC4) to remain at 0V. However, a switch (or relay) can be connected between RC4 and Vdd (5V) so that when the switch is pressed (or the relay is activated) the pin is short circuited to 5V.

Ted

• Geoffry says:

Thank you for everything. The resistor worked perfectly, I still have some minor adjustments to make but I’m on my way. Thanks again!

• batchloaf says:

You’re welcome! Well done on getting it working and best of luck getting the rest of your system finished.

Ted

4. none says:

From datasheet : PWM Period = [(PR2) + 1] • 4 • TOSC • (TMR2 Prescale Value)

Hi,
why you formula did not have “4″ in this calculation?

• batchloaf says:

The PIC18F4620 (and maybe all PIC18F chips) performs one machine instruction every four clock cycles. Tosc is the clock period (the time taken for one complete cycle of the clock oscillator). The default clock frequency for the PIC18F4620 is 1MHz, so the default clock period is 1us. Therefore, by default (unless you change the clock frequency), the PIC18F4620 performs one machine instruction every 4us. This is what we call the “instruction cycle”, usually written as “Tcy”. All delays, counter periods, pulse widths, etc are specified relative to the instruction cycle.

• The Seeker says:

OK, I understand that PWM period formula, but the datasheet also says the PWM pulse width = CCPR1L * Timer2 prescaler value * Tosc (not Tcy as you wrote). Why is that?

• batchloaf says:

Tosc is the period of the clock oscillator. Tcy is the time taken to execute each machine code instruction. Since this PIC performs one machine code instruction every four clock cycles, the relationship between Tosc and Tcy is just

`Tcy = 4 * Tosc`

The version of the formula given in the datasheet just expresses the PWM period in terms of Tosc, rather than Tcy, so there’s an extra factor of 4 included.

5. help says:

i follow your way to write a code for 16f1829, but i no get a correct Hz
i want 50Hz PWM with 50% duty cycle, can u help?

• batchloaf says:

Hi “help”,

I’ve just posted an example of a 50Hz square wave (i.e. 50% duty cycle) for the PIC16F1829. Here’s the link:

http://batchloaf.wordpress.com/2013/06/05/50hz-square-wave-example-for-pic16f1829/

Have a look and see if that will solve your problem. I’ve done it the lazy way – just bit-banging in a while loop with a delay to set the frequency. Is that enough for what you’re doing or do you need to do it using the PIC’s dedicated PWM facility? If so, I can have a look at adding a second example that does that.

Ted

6. help says:

Hello,
i try coding 16f1829 for generate the 50Hz PWM but no success.
i want 50Hz and 50% duty cycle, can you help and guide?

7. franco attolini says:

Ted,
Great example!
Im new to pic programming and now im trying to program a 10f322 in C to get a PWM do you have an example, im geting crazy.

Thank you .

Franco

8. mnoufalc says:

could you explain something more about PWM resolution. How does it get limited depending on the value of PR2 ?

• batchloaf says:

In this example, the generation of the PWM output is driven by Timer 2, which is basically just a register (well, a special function register) called TMR2 that counts up automatically in the background driven by the system clock. Depending on how you configure it, it can increment (increase its value by 1) every single instruction cycle, or every 16 instruction cycles, or every 64 instruction cycles or every 256 instruction cycles.

Another special function register called PR2 (Period Register for Timer 2) controls the maximum value for TMR2. Whenever TMR2 reaches the same value as PR2, it resets back to zero and then continues counting. If a PWM signal is generated based on TMR2, a pulse will normally start whenever TMR2 resets to zero and it will end when TMR2 reaches another value (e.g. CCPR1) which should be less than PR2. So the value of CCPRx controls the pulse width.

The range of possible values for CCPR1 ranges from 0 to PR2, which means there you can select from a total of (PR2+1) different pulse width settings. Therefore, the higher PR2 is, the more different values you can select for the pulse width. In many applications, the actual individual pulses will subsequently be filtered out, either using an actual filter, or because the thing being controlled cannot respond fast enough to reveal the individual pulses, so that all that is important is the average level. In such a situation, if PR2 is bigger, the number of possible pulse widths is greater and therefore the average level can be controlled with higher resolution.