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.
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
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.