Page 1 of 1

Pulse Width Modulation Tutorial

Posted: Sun Sep 20, 2009 6:36 am
by Reginald
Introduction
Pulse width Modulation or PWM is one of the powerful techniques used in control systems today. They are not only employed in wide range of control application which includes: speed control, power control, measurement and communication. This tutorial will take you through the PWM basics and implementation of PWM on 8051 and AVR microcontrollers.

Basic Principal of PWM
Pulse-width Modulation is achieved with the help of a square wave whose duty cycle is changed to get a varying voltage output as a result of average value of waveform. A mathematical explanation of this is given below.
square-wave.gif
square-wave.gif (4.22 KiB) Viewed 23994 times
Consider a square wave shown in the figure above.

Ton is the time for which the output is high and Toff is time for which output is low. Let Ttotal be time period of the wave such that,
eq1.gif
eq1.gif (609 Bytes) Viewed 23994 times
Duty cycle of a square wave is defined as
duty-cycle.gif
duty-cycle.gif (940 Bytes) Viewed 23994 times
The output voltage varies with duty cycle as...
eq2.gif
eq2.gif (568 Bytes) Viewed 23994 times
eq3.gif
eq3.gif (826 Bytes) Viewed 23994 times
So you can see from the final equation the output voltage can be directly varied by varying the Ton value.

If Ton is 0, Vout is also 0.

if Ton is Ttotal then Vout is Vin or say maximum.

This was all about theory behind PWM. Now lets take a look at the practical implementation of PWM on microcontrollers.

8051 PWM Example

Idea Behind Implementation
The basic idea behind PWM implementation on 8051 is using timers and switching port pin high/low at defined intervals. As we have discussed in the introduction of PWM that by changing the Ton time, we can vary the width of square wave keeping same time period of the square wave.

We will be using 8051 Timer0 in Mode 0. Values for high and low level will be loaded in such a way that total delay remains same. If for high level we load a value X in TH0 then for low level TH0 will be loaded with 255-X so that total remains as 255.

ASM Code for Timer setup for PWM

Code: Select all

        PWMPIN EQU P1.0         ; PWM output pin
PWM_SETUP:
        MOV TMOD,#00H           ; Timer0 in Mode 0
        MOV R7, #160            ; Set pulse width control
        ; The value loaded in R7 is value X as
        ; discussed above.
        SETB EA         ; Enable Interrupts
        SETB ET0                ; Enable Timer 0 Interrupt
        SETB TR0                ; Start Timer
        RET
ASM Code for Interrupt Service Routine

Code: Select all

TIMER_0_INTERRUPT:
        JB F0, HIGH_DONE        ; If F0 flag is set then we just finished
                                ; the high section of the
LOW_DONE:                       ; cycle so Jump to HIGH_DONE
        SETB F0                 ; Make F0=1 to indicate start of high section
        SETB PWMPIN             ; Make PWM output pin High
        MOV TH0, R7             ; Load high byte of timer with R7
                                ; (pulse width control value)
        CLR TF0                 ; Clear the Timer 0 interrupt flag
        RETI                    ; Return from Interrupt to where
                                        ; the program came from
HIGH_DONE:
        CLR F0                  ; Make F0=0 to indicate start of low section
        CLR PWMPIN              ; Make PWM output pin low
        MOV A, #0FFH    ; Move FFH (255) to A
        CLR C                   ; Clear C (the carry bit) so it does
                                        ; not affect the subtraction
        SUBB A, R7              ; Subtract R7 from A. A = 255 - R7.
        MOV TH0, A              ; so the value loaded into TH0 + R7 = 255
        CLR TF0                 ; Clear the Timer 0 interrupt flag
        RETI                    ; Return from Interrupt to where
                                ; the program came from
In your main program you need to call this PWM_SETUP routine and your controller will have a PWM output. Timer Interrupt service routine will take care of PWM in the background. The width of PWM can be changed by changing the value of R7 register. In above example I am using 160, you can choose any value from 0 to 255. R7 = 0 will give you o/p 0V approx and R7 = 255 will give you 5V approx.

You can also make use of Timer1 if you want. And the output pin can be changed to whatever pin you want.

C Code for Timer setup

Code: Select all

//Global variables and definition
#define PWMPIN P1_0

unsigned char pwm_width;

void pwm_setup(){
        TMOD = 0;
        pwm_width = 160;
        EA = 1;
        ET0 = 1;
        TR0 = 1;
}
C Code for Interrupt Service Routine

Code: Select all

void timer0() interrupt 1 {
        if(!F0) {       //Start of High level
                F0 = 1; //Set flag
                PWMPIN = 1;     //Set PWM o/p pin
                TH0 = pwm_width;        //Load timer
                TF0 = 0;                //Clear interrupt flag
                return;         //Return
        }
        else {  //Start of Low level
                F0 = 0; //Clear flag
                PWMPIN = 0;     //Clear PWM o/p pin
                TH0 = 255 - pwm_width;  //Load timer
                TF0 = 0;        //Clear Interrupt flag
                return;         //return
        }
}
Now your normal 8051 is capable of PWM output.

AVR PWM Example
Most of AVR controllers have onchip PWM channel which makes PWM usage much simple and more accurate. AVR timers or counters can be used in PWM mode without disturbing the basic timer function. As in case of AT90S8515, Timer1 can be configured in PWM mode by setting PWM11 and PWM10 bits in TCCR1A register. Following modes are available for PWM:
tbl1.JPG
tbl1.JPG (11.65 KiB) Viewed 23994 times
The pre-scalar source for Timer/Counter1 can be selected with the help of clock select bits in TCCR1B register (more information please check datasheet at page 37).

Width of pulse is loaded in the timer output compare registers OCR1A (OCR1AH & OCR1AL) and OCR1B (OCR1BH & OCR1BL). Timer/Counter1 acts as an up/down counter, counting up from $0000 to TOP (see table below), where it turns and counts down again to zero before cycle is repeated. When the counter value matches the content of 10 least significant bits of OCR1A or OCR1B, the PD5 (OC1A)/OC1B pins are set or cleared according to the settings of COM1A1/COM1A0 or COM1B1/COM1B0 bits in Timer/Counter1 Control register (TCCR1A), see table below.
tbl2.JPG
tbl2.JPG (9.06 KiB) Viewed 23994 times
tbl3.JPG
tbl3.JPG (18.13 KiB) Viewed 23994 times
ASM Code

Code: Select all

;8-bit Non-Inverted PWM code example
.equ pulse_width = $40
;Pulse width can be changed from 0 to TOP
PWM_START:
        ldi temp, pulse_width   ;Load pulse width
        out OCR1AL, temp        ;OCR1A = Pulse width
        clr temp
        out OCR1AH, temp

        ldi temp, $81           ;8-bit PWM Mode
        out TCCR1A, temp        ;Non Inverted

        in temp, DDRD           ;Make PortD.5 as o/p
        ori temp, (1<<5)
        out DDRD, temp

        ldi temp, $1            ;Start PWM
        out TCCR1B, temp
        ret                     ;Return to main
        ;PWM will run in background automatically
C Code

Code: Select all

//Global variables and definition
#define PULSE_WIDTH 0x40

void pwm_start(){
        OCR1AL = PULSE_WIDTH;   //Load Pulse width
        OCR1AH = 0;
        DDRD |= (1<<5);         //PortD.5 as o/p
        TCCR1A = 0x81;          //8-bit, Non-Inverted PWM
        TCCR1B = 1;             //Start PWM
}
Most of AVRs have same set of registers for PWM setup.

Re: Pulse Width Modulation Tutorial

Posted: Fri May 19, 2017 12:54 pm
by DerrickL
Thanks for the tutorial Reginald.