
PIC Micorcontroller Pulse-Width Modulation Basics
For the background on this project see Introducing the BOLT PIC18F2550 Microcontroller Board.
As a newcomer to programming the Microchip PIC processors the lack of usable information on both the web and in the spec sheets proved challenging. Nowhere was this more so than with the pulse-width-modulation setup. My goal here is to produce working examples that make sense to those wanting to get into PIC programming, but are turned off by the lack of useful literature.
PICs have a strong point in following a certain pattern regardless of type and there are a lot of PICs out there. They add new features while retaining older hardware for the most part.
For my part I've worked with the 18F2550 in C and the 16F628 in assembly. Their PWM modules are the same. I wanted to learn to program these devices without reliance on user supplied files and find out directly how the hardware works underneath in order to interface various electronics components.
In addition learning to directly write the individual registers in C enables one to better understand assembly code which can be embedded into a C program. In cases such as the 16F628 with only 2k of user memory and 256 bytes of SRAM programming in C will overrun available resources in no time. So there it's all assembly language.

How to Work a Real World Example
Note the above illustration. The period of a waveform is the reciprocal of the frequency. So the period of 5 kHz = 1 / 5000 = 0.0002. Duty cycle is the ratio of on time divided by period times 100. So with a period of 200 uSec and an on time 50 uSec we get 50/100 * 100 = 50%. With those values the on time for 10% is 20 uSec. and for 90% is 180 uSec.
In the example below I calculate the values to produce a 5 kHz 50 percent duty cycle square wave. The PWM module only uses TMR2 and I switch the output on/off at 1 second intervals by changing TRISBbits.TRISB3 from output to input.
#include <p18cxxx.h> #include <delays.h> void init_18F2550(void); void delay_ms(int); // See c018i.c in your C18 compiler dir extern void _startup( void ); // For use of BOLT USB boot loader, otherwise set to 0x0000 #pragma code _RESET_INTERRUPT_VECTOR = 0x000800 void _reset( void ) { _asm goto _startup _endasm } #pragma code void main() { //user program init_18F2550(); TRISB = 0 ; // set PORTB as output PORTB = 0 ; // assures TRISBbits.TRISB3 = 0 /* * Using CCP2 module output on RB3 * RB3 must set as an output * PWM Period = [(PR2) + 1] * 4 * TOSC * (TMR2 Prescale Value) * configure CCP module as 5000 Hz PWM output * PWM period = 1 / 5000 = 0.0002 * crystal is 20 mHz but internal multipliers operates at 48 mHz. * Tosc = 1/ 48 mHz * TMR2 prescale = 16 * PR2 = 149 * Reading on my frequency counter 5 kHz */ // TMR2 period register for 5 kHz is 149 and prescale of 16. PR2 = 149; T2CON = 0b00000111 ; // 0x07 TMR2 is on and prescale / 16 /* bit 7 xx bits 6-3 postscale unused here bit 2 TMR2ON: Timer2 On bit 1 = Timer2 is on 0 = Timer2 is off bit 1-0 T2CKPS1:T2CKPS0: Timer2 Clock Prescale Select bits 00 = Prescaler is 1 01 = Prescaler is 4 1x = Prescaler is 16 */ //The eight MSbs of the duty cycle are found in CCPR2L. CCPR2L = 0b00000000 ; // 0x00 PWM off /* PWM duty cycle is 10-bit value of 0-1023 The eight MSbs of the duty cycle are found in CCPR1L. CCP2CONbits.DC1B1 is bit 1 CCP2CONbits.DC1B0 is bit 0 bits 3-0 1100 = PWM mode: P1A, P1C active-high; P1B, P1D active-high See page 151 in spec sheet // only using 8 MSBs in this demo */ CCP2CON = 0b00001100 ; // 0x0C /* To calculate duty cycle: PWM duty cycle = (DCxB9:DCxB0 bits value) * Tosc * (TMR2 prescale value) Tosc = 1 / 48 mHz TMR2 prescaler = 16 Frequency = 5 kHz; PWM period = 1 / 5000 = 0.0002 For a duty cycle of 50 percent: 0.0002 * .5 = value * 2.083 e-8 * 16 value = 75 for a 50% duty cycle. */ CCPR2L = 75; // set duty cycle // period reading on my frequency counter is 50% for(;;) { TRISBbits.TRISB3 = 0; // turn on output delay_ms(1000); // on for 1 second // setting TRISBbits.TRISB3 = 1 turns off output TRISBbits.TRISB3 = 1; // turn off output delay_ms(1000); // off 1 second } } // end main() //Ports initialized A, B, C void init_18F2550(void) { ADCON1=0x0F; // disables converters A/D CMCON=7; TRISB=0; //PORTB are outputs PORTB=0; // off LEDS TRISA=0X30; //RA4,RA5 are inputs. RA0,RA1,RA2,RA3 outputs TRISC=0X0F; //RC0,RC1 are inputs (MICROSWITCHES) INTCON2bits.RBPU=0; //pull-up resistors on port B (RB4...RB7). } void delay_ms(int i) { long int j; for(j=0;j<i;j++) { //48 MHZ, DELAY OF 1 MS APROX. Delay1KTCYx(12); } }
See How I got into Electronics
Videos, Links, Downloads for the PIC18F2550 BOLT
- Introducing the BOLT PIC18F2550 Microcontroller Board
- PIC18F2550 BOLT with Serial LCD Display
- Using the MAX7219 with the 18F2550 Programs:
- MAX7219 Display Driver and a PIC Micro Controller
- MAX7219 Display Controller in the Non-Decode Mode with PIC
- Using TMR0 and Interrupts on the PIC18F2550
- YouTube Videos:
- My YouTube Channel
- MAX7219 display controller with 8X8 LED Matrix
- Programming the MAX7219 and 7-Segment Display
- Connecting PIC18F2550 to Parallel LCD Display
- Connecting PIC18F2550 to Serial LCD Displays
- Downloads:
- lewislcd.h My LCD H file
- Schematic Serial LCD
- BOLT_Template.zip
- Bolt Getting Started (pdf)
- Assembly language projects using PIC16F628:
- Exploring the Microchip PIC in Assembly
- Using a Microchip PIC with TLC548 Serial ADC
- Controlling PIC Pulse Width Modulation with a Serial ADC
- Using TMR0 on a PIC with Interrupts
- External Clock Crystal with PIC16F628 TMR1 Generates Interrupt
- PIC Using Rotary Encoder to Operate Stepper Motor
- PIC16F628 Pulse Width Modulation Controls Brightness of LED
- Another way to Turn On-Off PWM in a PIC
- TLC548 Serial ADC Spec. Sheet
Web site Copyright Lewis Loflin, All rights reserved.
If using this material on another site, please provide a link back to my site.