Controlling the Brightness
of an LED
Pulse Width Modulation
This
tutorial is part of the Microcontroller Beginner Kit.
It is easy to use a microcontroller to turn LEDs
on and off in almost any pattern you want. (Click
here for more information.) 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. Look at 8051hardware.pdf
for more information on the different modes. 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.
TIMER_0_INTERRUPT:
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
HIGH_DONE:
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
LOOP:
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.
To get all the parts required
to do this project buy the Microcontroller Beginner Kit
Back To
Tutorials Menu
Previous
- Temperatur Sensor
Next -
Using the LCD Module
Catalog
-- Support -- Privacy
Policy -- About Us
This page last updated on January
11, 2005.
|