Home   |  Schematics |  Products |  Tutorials  |  Datasheets  |  Robotics   |   Download    |   Link Exchange

Direct Current
Alternating Current
Digital Electronics
PC Architecture
Electronics Dictionary

Associations and Societies
Component Manufacturers


Electronics Symentics

Microcontroller Advanced Kit - Pulse Width Modulation

It is easy to use a microcontroller to turn LEDs on and off in almost any pattern you want. But you can only turn the LED on and off. So what if you want to control the brightness of the LED? The same problem comes up in robotics where you want to control the speed of a motor with a microcontroller. It is not good enough to just turn the motor on and off. To control the brightness of the LED or speed of the motor you have to control the amount of current going through the device. But how? One solution that may occur to you is to quickly turn the LED or motor on and off. The current only flows when the output is low (for microcontrollers LED circuits are usually wired so current flows into the microcontroller when the output is low, as shown in the tutorial at http://www.iguanalabs.com/1st2051.htm). The output of your microcontroller will look like the following square wave.

If you turn an LED or motor on and off fast enough then it will appear to stay on continuously and since there is less current flowing overall the LED will appear less bright and the motor will run at a slower speed. With this solution you can make the LED flash on and off as slow as 30 times a second but any slower and you start to see the LED blinking which is not the desired result. Or, for the motor, it will lose its smooth operation and get jerky. The solution does not work very well because the LED is still rather bright at 30 times a second.

We are on the right track but rather than changing the number of times the output goes on and off, we change how long the output stays on and off. Let's take a closer look at one output cycle. An output cycle consists of a low period, tlow and a high period, thigh. tlow + thigh = T, where T is the period (length of time) for one output cycle. thigh is also called an output pulse, or just pulse.

We will always keep T the same so that there are always the same number of output cycles per second. If we increase the width of thigh then we must decrease tlow to keep T the same. If we decrease thigh then we must increase tlow. For the case that we make thigh small then the output looks like the following.

You can see that the output is 0 most of the time and the LED or motor will be on most of the time.

For the case that we make thigh large then the output looks like the following.

The output is Vcc most of the time which turns off the LED. The current only flows through the LED for the brief time that the LED is on during tlow. But since we are still turning the LED on and off very fast (we will use about 100 times a second in the examples below), you can not see the LED blinking and it appears very dim. The total current that flows through the LED is low. For the motor it will smoothly turn at a low speed.  So we can control the brightness of the LED or the speed of a motor by changing the width of thigh. This is the secret of Pulse Width Modulation.

Making It Work

Next we will see how to make this work in an 8051. You can use the hardware setup as shown in either the first microcontroller project for the 2051 or the 8051. The software examples work for either of the hardware setups.You can also refer to those links for free tools and more information on working with assembly language files.

The first example is pwmled.asm. This example uses two delay routines. One delay is used to control tlow and the other delay is used to control thigh. The example is set to minimize tlow and maximize thigh to make the LED appear very dim. To make the LED brighter you can decrease R4 and increase R3. This example works fine and shows an easy way to control the pulse width. The biggest disadvantage is that it assumes you will be not be doing anything else in your program. If you try to do some other processing you will affect the timing of the pulses.

A better solution is in pwmled2.asm. This example uses one of the microcontrollers built in timers to control the pulse width. A timer can be used to create delay routines while the processor is free to run other parts of your program. It is an independent piece of hardware that you assign a task to and it goes off and does its work without using the main processor. When it finishes its task it lets you know by generating an interrupt. You can then give it another task and get back to the other processing you were doing.

Register R7 is used to control the pulse width. To increase the brightness of the LED, increase R7. You can use the example code as is and adjust R7 to control the Pulse Width without going through the rest of the detailed explanation of how the code works.

We will set up Timer 0 as an 8 bit timer so it can have values of 0 to 255. In order to keep T constant we will make the total time T correspond to 255 counts in the timer. The thigh period will count from R7 to 255. The tlow period will count the rest of the 255 counts. The timer is designed to count up from some initial value and then interrupt. We just use R7 as the initial value for counting thigh. The initial value for tlow is more complicated. Lets say the initial value for tlow is X. We want R7 + X = 255. So X  =  255 - R7. In the actual program we find X by using the subtract command SUBB.

Timer 0 Setup

There are a couple of things we need to do to set up Timer 0 to tell it how to behave before we start using it. First we modify the Timer Mode Control Register, TMOD, to set Timer 0 to Mode 0.

MOV TMOD,#00H      ; set timer 0 to Mode 0 (8 bit Timer with 5 bit prescalar)

There are several modes that you can use to give the timers different behaviors. The basic operation of the Timer is to increase its value by one on each machine cycle. (A machine cycle is equal to 12 clock cycles.) In Mode 0 there is a 5 bit prescalar. This means the timer counts 32 machine cycles (5 bits goes from 0 to 32) before increasing its stored value. This means that there are 32 machine cycles for each count of the timer. If the crystal is 11.0592 MHz then each machine cycle is 0.000001085 seconds and each timer count is 32 * 0.000001085 = 0.00003472 seconds per count. If there are 255 counts per output cycle then there will be about 113 output cycles per second.

There is also a control to turn the timer on and off. We will turn the timer on and just let it run freely. The command to turn on Timer 0 is

SETB TR0           ; turn on timer 0

We also need to turn on the Timer 0 interrupt so that it will tell us every time the 8 bit value has gotten to 255 and turned over to 0. We need to set the bit EA to enable the interrupts. When this bit is cleared (0) it turns all the interrupts off. (It can be useful to turn all the interrupts off if you are doing something important and don't want to be interrupted.)

SETB EA            ; Enable Interrupts (each individual interrupt must also be enabled)

And then we must set the bit ET0 to specifically turn on the interrupt for Timer 0.

SETB ET0           ; Enable Timer 0 Interrupt

Using the Timer

Now we can load a value into the 8 bit timer register, TH0, and it will run freely until it "overflows". The overflow occurs when it is at its maximum value of 255 and on the next count goes back to 0. This is the same as what would happen to the mileage meter in your car when it reaches all 9s and "flips over" to all 0s. The overflow triggers the Timer 0 interrupt and the processor stops whatever it is doing and goes to the point 0BH in its program. (0BH is the hex value 0B which is the 11th memory location) You can see in the program pwmled2.asm that we have used the ORG command to put a command in the 0BH location that jumps to our Interrupt Service Routine (ISR) for Timer 0. This just means that when the interrupt occurs the processor will go and process some code and then return to what is was doing before.

Below is the code that the processor goes through each time the Timer 0 interrupt occurs. Since we are using Timer 0 to time both tlow and thigh, we use a Flag (which is just a bit) to indicate whether we are currently timing tlow or thigh. We set the bit to 1 for thigh and 0 for tlow.

JB F0, HIGH_DONE    ; If F0 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 P1.0           ; Turn off LED
MOV TH0, R7         ; Load high byte of timer with R7 (our pulse width control value)
CLR TF0             ; Clear the Timer 0 interrupt flag
RETI                ; Return from Interrupt to where the program came from
CLR F0              ; Make F0=0 to indicate start of low section
CLR P1.0            ; Turn on LED
MOV A, #FFH         ; 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

The basic idea of the routine is fairly simple. First it checks to see if it just finished thigh or tlow.

If it was thigh then it jumps to HIGH_DONE and prepares for the tlow period. We set F0 to 0 to indicate we are timing tlow. Then we turn on the LED. Next we find the value to load into the 8 bit timer register TH0. Timer 0 will count up from there.

If it was tlow then we continue through LOW_DONE and set F0 to 1 to indicate we are timing thigh. Then we turn off the LED. Next we load R7 into the 8 bit timer register TH0 and Timer 0 will count up from there.

Main Code

The main part of the code does not have to do anything. In this example we just move 01 to R7 for the minimum brightness and then make an infinite loop with

MOV R7, #001H      ; set pulse width control to dim
AJMP LOOP          ;go to LOOP

The processor just sits there in an endless loop until the Timer 0 interrupt occurs. Then it goes off and goes through the Timer 0 interrupt routine and returns to the endless loop to wait for the next interrupt. If we had some other processing to do we could put that code in here in place of the endless loop and the processor could actually do something useful while it is waiting for the next interrupt.

Frequently Asked Questions

What is a duty cycle?

Duty cycle is a term used to describe the output pulse. It is given as a percentage such as 80%. The percentage tells you what percent of the output cycle is high. So for a duty cycle of 80% thigh would be 80% of T and tlow would be 20%. To find the duty cycle, use the formula

Duty Cycle = thigh / (thigh + tlow)

Why does the interrupt go to 0BH?

The 8051 hardware is pre wired so that when an interrupt occurs the hardware automatically jumps to a predefined location in memory. These locations can not be changed. They are

   03H    ;external interrupt 0
   0BH    ;timer 0 interrupt
   13H    ;external interrupt 1
   1BH    ;timer 1 interrupt
   23H    ;serial port interrupt

Some versions of the 8051 have more interrupts and they are located close to these. These locations are all rather close together. There is not enough room to actually write an interrupt service routine so the general solution is to put a jump at each interrupt location you are using that jumps to the interrupt service routine which can be anywhere later in the program memory. Note that no interrupt service routine is needed for the interrupts that are not used in your program. In the example programs a return instruction RETI is put at each interrupt location for the rare case that some glitch happens and the hardware ends up at that location.

Home  Products  Tutorials   Schematics   Robotics   Resources   Radio Stuff    Career    Download   Link Exchange

HTML Sitemap   XML Sitemap

Terms & Conditions  Privacy Policy and Disclaimer