Pulse width modulation (PWM) is a widely used modulation technique not only in communication systems but also high current driving applications like motor drivers, LED drivers etc. This tutorial explains how to generate pulse width modulation using PIC18F4550.
PWM is a digital modulation technique widely used for coding a digital data into a pulsating signal which usually looks like a square wave. The applications such as motor speed control, for encoding messages in telecommunication systems and for controlled switching in switch mode power supplies and for sound synthesis in audio amplifiers etc.
PWM uses a rectangular pulse train whose modulation results in the average value of the pulse sequence.
The period is the total length of the pulse and it is measured in time. Pulse repeats over this defined time, which is referred to as a period.
Duty cycle is the percentage of ON time over the total period of the pulse. For example, if the period of the pulse is 100 ms and the ON time is 50 ms, then the period is (50 ms/100 ms)% which is 50%. A 50% pulse is a square wave whereas 100% pulse is equal to setting it to 5V.
So we can define,
Duty cycle = (d/t) x 100 percentage
where d = ON time and t = period
PIC18F4550 devices all have two CCP (Capture/Compare/PWM) modules. Each module contains a16-bit register, which can operate as a 16-bit capture register, a 16-bit compare register or a PWM master/slave duty cycle register which is primarily controlled by timers modules.
CCP stands for capture/compare and PWM. It is a hardware module inside the PIC microcontroller helps to trigger events based on time.
Capture mode allows us a duration based timing of an event. This circuit gives information regarding the current state of a register which constantly changes its value. In this case, it is the timer TMR1 register.
Compare mode compares values contained in two registers at some point. One of them is the timer TMR1 register. This circuit also allows the user to trigger an external event when a predetermined amount of time has expired.
PWM module generates the rectangular pulses whose duty cycle and frequency can be varied by altering the PWM registers.
Here in PIC18F4550, Port C pins RC1 and RC2 acts as PWM output pins.
Following steps are needed to setup the PWM module in PIC18F4550.
The PWM period is set by writing the PR2 register. So the value can be calculated by using this formula.i.e,
PWM period = [(PR2) + 1] x 4 x Tosc x (TMR2 prescale value)
For example, we use a 20MHz clock and the O/P frequency is 2KHz
where PWM period = 1/ Frequency (that will be 1/2000 = .0005)
.0005 = (PR2 + 1) x (1 / 20000000) x 16
PR2 + 1 = (.0005 x 20000000) / 16
PR2 = 624
PR2 = 0x270 ( 624 in hex)
PR2 = 0x270; // Set the PWM period
The PWM duty cycle is specified by writing to the CCPRxL register and to the CCPxCON<5:4> bits. Up to 10-bit resolution is available. The CCPRxL contains the eight MSbs and the CCPxCON<5:4> bits contain the two LSbs. This 10-bit value is represented by CCPRxL: CCPxCON<5:4>. The duty cycle will be in the range of 0 to 1024.
PWM Duty Cycle = (CCPRXL:CCPXCON<5:4>) x Tosc x (TMR2 Prescale Value)
For example, for setting up PWM module 1 of duty cycle value 16, we need to write first 8-bit to CCPR1L and LSB two bits in CCP1CON<5:4>. In the above example, we’ve used a prescaler value of 16 (0b0000010000). So CCPR1L will become 00000100 and CCP1CON<5:4> will be 00.
CCPR1L = 0x05; // PWM duty cycle - first 8-bits (MSb) CCP1CON &= (0x00 <<4); // PWM duty cycle - last 2-bits (LSb)
In this step, we need to set the pins used by PWM module as output. In our case, it is the RC2 pin.
/*set RC2 as PWM out pin*/ TRISCbits.RC2 = 0; // TRISC &=0xFD
Prescalar value is set in TMR2 register. After setting up the register, enable the timer2 module by writing to T2CON.
TMR2 = 0x0F; // Timer2 initial value, if needed T2CONbits.T2CKPS0 = 1; // Prescalar values 1:16 T2CONbits.T2CKPS1 = 1; // Prescalar values 1:16 T2CONbits.TMR2ON = 1; // Turn ON the timer after setting up everything
In this step, we are configuring the CCP module for PWM operation. We can do this by CCP1CON mode selection bits (bits 3-0).
CCP1CON &= 0xCF; // 5,4 bits zeroed (DC1B1:DC1B0 = 00) CCP1CON |= 0x0C; //PWM mode ( CCP1M3:CCP1M0 = 1100)
By using the above register configurations, we can generate PWM output of 2KHz.
Below is the sample firmware using above mentioned settings. They are arranged as two functions, one for initialization and other for setting duty cycle.
#include #include "config.h" // Configuration bit settings header file void PWM1_Init(unsigned char period){ TRISC &=0xFD; // Set RC2 as output /* CCP PWM mode */ CCP1CON &= 0xCF; // 5,4 bits zeroed (DC1B1:DC1B0 = 00) CCP1CON |= 0x0C; // PWM mode ( CCP1M3:CCP1M0 = 1100) /* Timer2 configuration */ PR2 = period; // configure timer2 period T2CON = 0x02; // Set prescalar 16 TMR2ON = 1; // timer2 on } void PWM1_setDC(unsigned int dutycycle){ CCPR1L = dutycycle>>2; // PWM duty cycle - first 8-bits (MSb) CCP1CON &= 0xCF; // 5,4 bits zeroed (DC1B1:DC1B0 = 00) CCP1CON |= ((dutycycle<<4)&0x30); // PWM duty cycle - last 2-bits (LSb) in CCP1CON 5,4 bits } void main() { /*open PWM at 2KHz*/ PWM1_Init(0x270); /*set duty cycle 0 - 1023 range */ PWM1_setDC(512); while(1); // Infinite run }
The above code doesn’t set an initial value at TMR2. If the need arises, set it.
Microchip’s XC8 has a PWM peripheral library which is very easy to use. It has a separate library for timers and PWM, includes them separately and calls the functions with necessary arguments.
/*include Header files */ #include <xc.h> #include "config.h" //configuration bit settings #include <plib/timers.h> #include <plib/pwm.h> /*The speed of your internal(or)external oscillator*/#define _XTAL_FREQ 20000000 void main() { /*set RC2 as PWM out pin*/ TRISCbits.RC2 = 0; /*configuring Timer 2 and set as clock source, at 1:16 prescalar */ Timer2Config = T2_PS_1_16; OpenTimer2(Timer2Config); /*open PWM at 2KHz*/ OpenPWM1(0x270); /*set duty cycle 0 - 1023 range */ SetDCPWM1(512); while(1); //always run }