| |
 |
The 8051
|
|
|
|
| |
|
| |
|
| |
Interrupts |
| |
|
| |
Revision of Timers |
| |
In an earlier section we looked at the 8051 timers and how we could
use them to generate a pulse train of a particular frequency on a port
pin (for example). |
| |
Below is a program for generating a pulse train
of 10KHz on port 1 pin 0. |
| |
|
| |
MOV TMOD,
#02H ;initialize timer 0 as 8-bit auto-reload timer
MOV TH0, #0CEH ;set timer 0 high-byte to produce 50us delay (assuming
system clock frequency of 12MHz)
SETB TR0 ;start timer 0
|
| waitForOverflow: |
JNB TF0,
waitForOverflow ;if timer 0 overflow bit is not set, repeat this
instruction
CLR TF0 ;reset timer 0 overflow bit
CPL P1.0 ;invert (complement) port 1 pin 0
JMP waitForOverflow
|
|
| |
|
| |
Busy Waiting |
| |
The above program implements what is known as busy
waiting. In the line JNB
TF0, waitForOverflow the CPU is continuously executing the
same instruction - testing the overflow bit of timer 0 and waiting for
it to change to 1. In other words, the CPU is busy testing the
bit while waiting for its value to change. |
| |
Last year, when we wrote programs to exercise
the timers and the serial port, we always used busy waiting. The CPU
would sit in a loop, testing a bit (the timer overflow bit, the data
received bit, the data transmitted bit) waiting for it to be set. The
setting of the bit announced an event. If it was a timer overflow bit
it announced the timer's overflow to zero, if it was the serial port
transmit interrupt bit it announced the complete transmission of a
byte, if it was the serial port receive interrupt bit it announced the
arrival of an entire byte of data. All of these are events. The CPU
waits for these events, continuously testing the appropriate bit and
waiting for it to be set. |
| |
|
| |
Advantages of busy waiting: |
| |
- Easy to implement.
- Relatively easy to test - step through the program and at
each point it can be determined what the next instruction to be
executed will be.
|
| |
Disadvantages of busy waiting: |
| |
- The CPU is tied up with a mundane task when it could be off
doing something more useful.
- Continuous CPU operation is heavy on power consumption -
not good for battery operated devices.
- If more than one event needs to be tracked busy waiting
becomes difficult to implement (need to test more than one bit in the
busy waiting loop and deal with whichever event occurs. While dealing
with this event, another event may occur and be missed because the CPU
was not in its busy waiting loop).
- If some events have higher priorities than others and must
be dealt with before lower priority events, busy waiting becomes almost
impossible to implement.
|
| |
|
| |
|
| |
Interrupts |
| |
An interrupt is the occurrence of an event that
causes a temporary suspension of a program while the event is serviced
by a section of code known as the interrupt service routine. |
| |
Modern operating systems use interrupts to
produce the illusion of a multiprocessor computer. A computer may be
running many applications at the same time, say a word processor, a CD
player and a web server. All of the applications think they
have sole access to the CPU. However, since there is only one processor
in the computer (in most cases) then this cannot be the case. |
| |
In reality, they are all sharing the CPU. The
operating system (OS) hands the CPU to one process (in OS terms, an
programs or applications are known as processes) for a set amount of
time (perhaps milliseconds) and then hands it over to the next process
for a set amount of time, and so on until it gets back to the initial
process. This form of CPU sharing is known as round robin scheduling
and we will talk about it more when we deal with the section on
real-time operating systems. |
| |
|
| |
How is one process stopped so that another can
get the CPU? After all, the process has the CPU, therefore the
operating system itself is not running and therefore it cannot stop the
running process. The answer is an interrupt. The running process is
interrupted after its allotted CPU time has elapsed and the operating
system takes the CPU so that it can then hand the CPU over to the next
process. The interrupt is generated by the hardware. |
| |
The operating system uses a hardware
timer to generate this interrupt. The sequence is as follows: |
| |
- The OS initializes the timer to count for a specified time.
- The OS starts the timer.
- The OS hands the CPU over to the next process in the list
(ie; the list of processes that are waiting to use the CPU). The OS is
now no longer running.
- The timer overflows and generates an interrupt.
- The interrupt forces the program counter to jump to the
interrupt service routine (ISR).
- The ISR jumps to step 1 above.
|
| |
|
| |
Desktop operating systems are not the only
software systems to make use of interrupts. They are heavily used in
embedded systems. Most systems deal with external events through
interrupts. For example, when you press a key on your mobile phone, the
keypad sends an interrupt signal to the microcontroller. The
microcontroller is then forced to jump to the ISR, regardless of what
it was doing in its main program. |
| |
The diagram below illustrates a system program
flow with interrupts. |
| |
 |
| |
|
| |
Interrupt events can internal or external. The
interrupt described in the OS above is an internal interrupt generated
by a timer overflow. An example of an external interrupt is a signal
generated by a key press on a keypad. |
| |
|
| |
8051 Interrupts |
| |
The 8051 has five interrupt sources. |
| |
- Two external interrupts are provided through pins INTO-bar
and INT1-bar, which are the alternate functions of port 3 pin 2 and
port 3 pin 3, respectively.
- Two internal interrupts are generated by timer 0 overflow
and by timer 1 overflow.
- The serial port on the 8051 can generate an interrupt when
a byte has been transmitted or when a byte is received.
|
| |
The interrupt flag bits are detailed below. |
| |
| Interrupt |
Flag |
Location |
| External 0 |
IE0 |
TCON.1 |
| External 1 |
IE1 |
TCON.3 |
| Timer 0 |
TF0 |
TCON.5 |
| Timer 1 |
TF1 |
TCON.7 |
| Serial Port Receive |
RI |
SCON.0 |
| Serial Port Transmit |
TI |
SCON.1 |
|
| |
|
| |
When an interrupt occurs the following happens:
|
| |
- The current instruction completes execution.
- The PC is saved on the stack (ie; the address of the next
instruction).
- The address of the ISR for the interrupt is loaded into the
PC.
|
| |
Interrupt Vectors |
| |
When an interrupt occurs the address of the
interrupt service routine is loaded into the PC. This address is known
as the interrupt vector. |
| |
The table below details the interrupt vectors
for the 8051. |
| |
|
| |
| Interrupt |
Flag |
Vector |
| System reset |
RST |
0000H |
| External interrupt 0 |
IE0 |
0003H |
| Timer 0 |
TF0 |
000BH |
| External interrupt 1 |
IE1 |
0013H |
| Timer 1 |
TF1 |
001BH |
| Serial port |
RI or TI |
0023H |
|
| |
A system reset is a special type of interrupt.
It interrupts the running program and loads the PC with the vector
address 0000H. This is the address the microcontroller begins with on
power-up. Therefore, reset is similar to powering down and the powering
up the system. |
| |
When an interrupt in the 8051 occurs, the vector
address, as shown above, is loaded into the PC. For example, if timer 1
overflows (and interrupts are enabled; we'll talk more about enabling
and disabling interrupts shortly) the PC is loaded with the value
001BH. The programmer must ensure the ISR for timer 1 is placed at this
address. |
| |
|
| |
Not much room for the ISRs |
| |
When you examine the vector table above you will
notice there are only eight memory locations between the vectors. If
our ISRs are short, this is not a problem. For example, if we need an
ISR for dealing with timer 1 and we also need one for dealing with the
serial port, then the timer 1 ISR, placed at location 001BH, cannot
take up more than eight memory locations because it would then take up
the space reserved for the serial port ISR. |
| |
The solution is simple. At location
001BH we put a jump to somewhere else in code memory and at this point
we put our timer 1 ISR. |
| |
|
| |
Enabling and Disabling Interrupts |
| |
As can be seen from the interrupt vector table
above, the timer interrupt flags are TF0 and TF1 and the serial
interrupt flags are RI and TI. We used these flags during the second
year of the course. For example, we waited for timer 0 to overflow by
testing the state of the timer overflow flag, TF0. If this is the case,
why didn't the timer overflowing (setting TF0) cause an interrupt? |
| |
The answer is simple: we did not enable
interrupts. |
| |
On power-up or reset all interrupts are
disabled. To enable interrupts we set the appropriate bits in the
interrupt enable SFR. This register is detailed below. |
| |
| Symbol |
Bit Number |
Description |
| EA |
7 |
Enable/disable all interrupts. If this bit is cleared
all interrupts are disabled. If it is set each interrupt source is
individually enabled or disabled by setting or clearing the appropriate
enable bit, as detailed below. |
| -- |
6 |
|
| -- |
5 |
|
| ES |
4 |
Enable/disable serial port interrupts (set to enable,
clear to disable). |
| ET1 |
3 |
Enable/disable timer 1 overflow interrupt (set to
enable, clear to disable). |
| EX1 |
2 |
Enable/disable external 1 interrupt (set to enable,
clear to disable). |
| ET0 |
1 |
Enable/disable timer 0 overflow interrupt (set to
enable, clear to disable). |
| EX0 |
0 |
Enable/disable external 0 interrupt (set to enable,
clear to disable). |
|
| |
To enable an interrupt we must set the EA bit
and then set the appropriate interrupt enable bit. For example, to
enable the timer 0 overflow interrupt we would write the following code: |
| |
|
| |
To disable an interrupt we simply clear the
appropriate interrupt enable bit. For example, to disable the timer 0
overflow interrupt: |
| |
|
| |
|
| |
Why is there a global enable bit (EA)? |
| |
You may be curious as to why the EA bit exists
at all. It seems pointless to have to set it and then set the
individual enable bit to enable an interrupt. However, there is a very
good reason for having the EA bit. There are sections of code known as
critical sections (we will deal with these when we discuss operating
systems in the Embedded Software class) that must not be interrupted.
In other words, a critical section of code must be allowed to complete
in entirety without being interrupted. To ensure this, when entering a
critical section, all interrupts are disabled (CLR EA). Then, when leaving
the critical section, the global enable bit is set (SETB EA). In this way,
whatever interrupts were enabled prior to entering the critical section
are again enabled when the critical section has been complete. |
| |
Take a look at the code below for an example. |
| |
SETB EA
;enable all interrupts
SETB ET0 ;enable timer 0 interrupt
SETB EX1 ;enable external 1 interrupt (remaining 3 interrupt sources
remain disabled)
.
.
.
; entering a critical section
CLR EA ;disable interrupts
; execute critical section code
SETB EA
; exit critical section
; interrupts ET0 and EX1 are again enabled (remaining 3 interrupt
sources remain disabled)
|
|
| |
|
| |
If we take the above code as an example, the
programmer writing the critical section code doesn't need to know which
interrupts are enabled. He/she simply disables all interrupts at the
start of the critical section and then sets the global enable bit on
exiting the critical section, therefore re-enabling whichever
interrupts had been enabled in the first place. |
| |
|
| |
Polling Sequence |
| |
What happens if two interrupts of the same
priority occur at the same time? (We will deal with interrupt priority
in a moment - for now take it that all interrupts have the same
priority.) The 8051 has a definite polling sequence that deals with
simultaneous interrupts. Each interrupt is serviced in the following
order: |
| |
External 0 followed by Timer 0
Overflow followed by External 1 followed by Timer 1
Overflow followed by the Serial Port. |
| |
Therefore, if for example an interrupt occurs on
timer 1 and external 1 at the same time, external 1 will be serviced
before timer 1. |
| |
|
| |
Interrupt Priority |
| |
In most systems, some events are more important
than others. For example, imagine a microwave oven in operation (ie;
heating food). Let's also imagine a user presses a key on the keypad
and opens the oven door at the same time. The interrupt caused by
opening the door is more important than the interrupt caused by the key
press (when the oven door is opened the microwave must immediately shut
down, regardless of what's being pressed on the keypad) and it should
be serviced first. |
| |
Therefore, microcontrollers are designed so
that interrupts can be prioritized. The 8051 has only two interrupt
priority levels, 0 and 1, with 1 being the high priority. On reset, all
interrupts are set at the low priority. To set an interrupt to high
priority we set the appropriate bit in the interrupt priority (IP) SFR,
as detailed below. |
| |
| Symbol |
Bit Number |
Description |
| -- |
7 |
|
| -- |
6 |
|
| -- |
5 |
|
| PS |
4 |
Serial port interrupt priority level. |
| PT1 |
3 |
Timer 1 interrupt priority level. |
| PX1 |
2 |
External interrupt 1 priority level. |
| PT0 |
1 |
Timer 0 interrupt priority level. |
| PX0 |
0 |
External interrupt 0 priority level. |
|
| |
An interrupt service routine (ISR) can itself be
interrupted. If a low priority interrupt is being serviced while a high
priority interrupt occurs, the low priority ISR is interrupted (in
exactly the same way as the main program is interrupted) and the high
priority interrupt is serviced. Once the high priority ISR completes
execution begins again at the next instruction in the low priority ISR. |
| |
An ISR cannot be interrupted by an interrupt of
the same or lower priority. |
| |
The 8051 has only two priority levels, but some
microcontrollers/microprocessors have more than two levels. If we
imagine for a moment a microcontroller that has eight priority levels 0
to 7, with 7 as the highest priority. In this case a level 5 ISR (for
example) can only be interrupted by interrupts at levels 6 and 7. |
| |
|
| |
Some Examples |
| |
Generating a 10KHz pulse train on port 1 pin 0
using interrupts. |
| |
A 10KHz signal has a cycle of 100us. Therefore
we need an interrupt to occur every 50us (pulse train of 50% duty cycle
- 50us at 1, 50us at 0). To achieve this we will enable timer 0
overflow interrupt and set up timer 0 so that it overflows every 50us. |
| |
Assuming a 12MHz system clock implies the timer
will step once every 1us - we need the timer to count from (256 - 50)
206 to overflow. We can do this with the 8-bit auto-reload mode. |
| |
ORG 0 ;
reset vector
JMP main ; jump above interrupt vectors
ORG 000BH ; timer 0 interrupt vector
CPL P1.0 ; invert port 1 pin 0
RETI ; return from interrupt
ORG 0030H ; main program entry point
|
| main: |
MOV
TMOD, #02H ; timer 0 in 8-bit auto-reload timer mode
MOV TH0, 0CEH ; put 206 decimal into TH0
SETB TR0 ; start timer 0
SETB EA ; global interrupt enable
SETB ET0 ; enable timer 0 interrupt
JMP $ ; do nothing but wait for interrupt
|
|
| |
|
| |
The code in boldface is the ISR. When timer 0
overflows (every 50us) the following occurs: |
| |
- PC is saved on the stack
- PC gets 000BH (interrupt vector for timer 0)
- Execution begins in ISR (in this case it simply inverts
P1.0)
- RETI takes the PC off the stack - execution beings again in
the main program where it left off (in this case the JMP $ instruction)
|
| |
The ORG statements are assembler
directives. They are not part of the 8051 code. They simply tell
the assembler where in code memory to place the following section of
code. The reset vector for the 8051 is 0. Therefore, when the 8051
powers up or is reset, the PC is loaded with 0 and the first
instruction executed is that stored at location 0. |
| |
When using interrupts we do not want our main
program to write over the area reserved for the ISRs. Therefore, our
first instruction (at location 0) is a jump to somewhere in code memory
above the ISR vectors (in the above example we jump to 0030H). |
| |
The ORG 000BH directive causes the
assembler to place the following piece of code (our timer 0 ISR) at
location 000BH. This is the timer 0 interrupt vector, ie; this is the
address automatically placed in the PC (by hardware) when an interrupt
occurs on timer 0 (and timer 0 interrupt is enabled). |
| |
|
| |
Servicing More Than One Interrupt |
| |
Let's say we wish to generate two pulse trains
of differing frequencies. For example, to generate a 7KHz pulse train
on P1.7 and a 500Hz pulse train on P1.6, we could use the following
code: |
| |
ORG 0 ;
reset vector
JMP main ; jump above interrupt vectors
ORG 000BH ; timer 0 interrupt vector
CPL P1.7 ; invert port 1 pin 7
RETI ; return from interrupt
ORG 001BH ; timer 1 interrupt vector
JMP timer1ISR ; jump to timer 1 ISR
ORG 0030H ; main program entry point
|
| main: |
MOV TMOD,
#12H ; timer 0 in 8-bit auto-reload timer mode, timer 1 in 16-bit mode
MOV TH0, #0B9H ; put (256 - 71) 185 decimal into TH0
SETB TR0 ; start timer 0
SETB TF1 ; force timer 1 interrupt
SETB ET0 ; enable timer 0 interrupt
SETB ET1 ; enable timer 1 interrupt
SETB EA ; global
interrupt enable
JMP $ ; do nothing but wait for interrupt
|
| timer1ISR: |
CLR
TR1 ; stop timer 1
MOV TH1, #0FCH
MOV TL1, #18H ; initialize timer 1 with (65536 - 1000) 64536 (FC18H
SETB TR1 ; start timer 1
CPL P1.6 ; invent port 1 pin 6
RETI
|
|
| |
A 7KHz pulse train has a cycle of 143us. With a
duty cycle of 50% the time delay required for generating the 7KHz pulse
train is 71us. This can be achieved using one of the timers in mode 2
(8-bit auto reload). The code above uses timer 0 to generate the 7KHz
pulse train on P1.7. |
| |
A 500Hz pulse train has a cycle of 2ms. Again,
with a duty cycle of 50% this results in a time delay of 1ms (1000us).
This is too long of a delay to be achieved using the 8-bit auto reload
mode. Therefore, timer 1 is put into 16-bit mode. However, this
requires the timer be initialized with the starting value (64536) each
time it overflows. To achieve this, the timer is first stopped in the
ISR and the value FC18H is loaded, the timer started again and the pin
(P1.6) is inverted. |
| |
Notice in the main program a timer 1 interrupt
is forced through software. This is to start the timer initially. Once
the interrupts are enabled execution will jump to the timer 1 ISR and
the timer is initialized and started. Then, 1ms later, a timer 1
overflow interrupt will occur and the timer will be initialized again. |
| |
Also note that timer 0 ISR is left at the reset
vector since it is such a short ISR. However, since there are only 8
bytes between each vector, timer 1 ISR is moved elsewhere in code
memory (after the main program in this example) and a jump to the ISR
is placed at timer 1's reset vector. |
| |
|
| |
What if both interrupts occur simultaneously? |
| |
If it happens that both timer 0 and timer 1
overflow at the same time, the polling sequence says the interrupt on
timer 0 will be serviced before timer 1. This is acceptable in this
situation because timer 0 is being used to generate the higher
frequency signal. If the P1.6 pin is not inverted exactly every 1ms
(because a few microseconds are lost while servicing timer 0) then this
is not very noticeable in the low frequency signal. |
| |
However, if timer 1 is being serviced when timer
0 overflows, timer 0 will have to wait because both interrupt sources
are at the same priority level (low level) and no ISR can be
interrupted by an interrupt of the same priority. This could present a
problem because the microseconds that are lost while servicing timer 1
would be noticeable in the higher frequency signal on P1.7. Therefore,
the main program should be altered as shown below: |
| |
| main: |
MOV TMOD,
#12H ; timer 0 in 8-bit auto-reload timer mode, timer 1 in 16-bit mode
MOV TH0, 0B9H ; put (256 - 71) 185 decimal into TH0
SETB TR0 ; start timer 0
SETB TF1 ; force timer 1 interrupt
SETB PT0 ; set timer 0 overflow to high priority
interrupt
SETB ET0 ; enable timer 0 interrupt
SETB ET1 ; enable timer 1 interrupt
SETB EA ; global interrupt enable
JMP $ ; do nothing but wait for interrupt
|
|
| |
Timer 0 is set to high priority. Therefore, if
timer 1 is being serviced and an overflow occurs on timer 0, timer 1
ISR will be suspended and timer 0 will be serviced. Timer 0 ISR is very
short (just invert P1.7 and return) so the time lost there will not be
noticed on the P1.6 signal, while at the same time the higher frequency
signal on P1.7 is kept accurate. |
| |
|
| |
|
| |
Serial Port Interrupts |
| |
There is only one serial port interrupt vector
in the 8051, at address 0023H. However, there are two serial port
interrupt sources - TI and RI. |
| |
- TI is set by hardware when an entire character has been
transmitted from SBUF down the serial line.
- RI is set by hardware when an entire character has been
received on the serial line and is waiting in SBUF to be read by the
program.
|
| |
Since the serial port can be used to transmit
and receive data at the same time, the serial port ISR must first check
to see which source caused the interrupt. |
| |
|
| |
The serial port flags are not automatically
cleared when vectoring to the ISR. |
| |
As we saw when dealing with interrupts on the
timers, the timer overflow flag is cleared by hardware when the CPU
vectors to the interrupt vector address. In other words, if the timer's
interrupt is enabled, when an overflow occurs the hardware
automatically resets the interrupt flag. |
| |
It is very important to note that this is NOT
the case with the serial port interrupt flags. Since the same vector is
used for both TI and RI, the ISR must first check to see which of the
two flags actually caused the interrupt and then clear this interrupt
flag. Clearing the serial port interrupt flags is the responsibility of
the software, not the hardware. |
| |
|
| |
Repeatedly Sending the ASCII Character Set on
the Serial Line |
| |
The program below shows how to send the ASCII character set
(excluding the control characters) down the serial line, starting with
the space character (20H) and progressing through the set to the last
character (7EH) and back to the start again. This program will loop
indefinitely. |
| |
A Baud rate of 9600 with a system
clock frequency of 12MHz is generated (see notes on the 8051 serial port for more
information on setting the serial port Baud rate). |
| |
ORG 0
JMP main
ORG 0023H ; serial port interrupt vector
JMP serialPortISR ; jump to serial port ISR
ORG 0030H ; main program entry point
|
| main: |
MOV TMOD,
#20H ; timer 1 in 8-bit auto-reload timer mode
MOV TH1, 0FDH ; high byte value to generate baud rate of 9600
SETB TR1 ; start timer 1
CLR SM0
SETB SM1 ; clearing SM0 and setting SM1 puts serial port into mode 1
SETB TI
; force serial port transmit interrupt
MOV A, #20H ; send ASCII space character first by initializing A to 20H
SETB ES ; enable
serial port interrupts
SETB EA ; global
interrupt enable
JMP $ ; do nothing but wait for interrupt
|
| serialPortISR: |
CJNE
A, #7FH, skip ; if A does not contain 7FH (the end of the ASCII
sequence) skip next line
MOV A, #20H ; if A contains 7FH replace it with the start of the
sequence (20H - the space character)
|
| skip:MOV
SBUF, A ; send character to serial port |
INC A
; increment to next character in ASCII sequence
CLR TI ; clear the transmit interrupt flag
RETI
|
|
| |
|
| |
In the above example, since we are only
transmitting data, when a serial port interrupt occurs we know it has
been caused by the transmission of an entire byte. Therefore, our
serial port ISR does not need to test to see whether the interrupt was
caused by TI or RI. It simply transmits the next character and then
clears TI. |
| |
|
| |
Transmitting and Receiving on the Serial Port |
| |
If our system transmits and receives data on the
serial port at the same time then we need to first check to see which
flag (TI or RI) actually caused the interrupt. |
| |
A typical serial port ISR for dealing with this
is shown below. |
| |
| serialPortISR: |
JNB
RI, testTI ; if RI is not set jump to testing TI
CLR RI ; if RI is set clear it
CALL dataReceived ; and call subroutine for dealing with received byte
|
| testTI: |
JNB
TI, endSerialPortISR ; if TI is not set jump to end
CLR TI ; if TI is set clear it
CALL dataSent ; and call subroutine that deals with an entire byte sent
(most likely to send another byte)
|
| endSerialPortISR: |
RETI
|
|
| |
In the above ISR, RI is tested to see if the
interrupt was caused by a received byte. If so it jumps to the
subroutine for dealing with this case, after first clearing RI. It then
checks to see if TI is set. Take note, a byte may have been sent at the
same time as a byte was received and therefore both RI and TI may be
set together. Therefore, the ISR must test TI even if it found RI was
set. If TI is set it jumps to the subroutine for dealing with this
case, after first clearing TI. |
| |
It is important to test RI before testing TI. If
RI is set then we must deal with this received byte as soon as possible
because another byte may be on the way, whereas it is less critical if
some time is lost between transmitting bytes. |
| |
|
| |
|
| |
External Interrupts |
| |
External interrupts occur as a result of a
low-level or negative edge on the INT0-bar or INT1-bar pins on the 8051
(INT0-bar is at P3.2 while INT1-bar is at P3.3). |
| |
The flags that generate these interrupts are IE0
and IE1 in the TCON register. |
| |
As stated above, the interrupt may be caused by either a
low-level or a negative edge on the INTx-bar pin. The choice of
low-level activation or edge activation can be programmed through the
IT0 and IT1 bits in the TCON register. For example, to set external
interrupt 0 as low-level activated and external interrupt 1 as edge
activated we could write the following:
|
| |
CLR IT0
SETB IT1
|
| |
The external interrupt pins are sampled (ie;
tested) once every machine cycle. If the interrupts are level
activated, the external source (the peripheral, such as a keypad, that
causes the interrupt) must ensure the interrupt pin is kept low for at
least one machine cycle. With the basic 8051 running at 12MHz the
machine cycle is 1us. |
| |
If the external interrupt is set to edge
activation then the external source must ensure the pin is kept high
for one complete machine cycle and then low for one complete machine
cycle. In this way, the CPU will see a negative edge on the
interrupt pin and set the flag accordingly (IE0 or IE1). |
| |
|
| |
Controlling the Level of Liquid in a Tank |
| |
The diagram below shows a tank interfaced (via
suitable electronic circuitry) to the 8051. The output value is
controlled via P1.0; a logic 0 opens the valve while a logic 1 closes
it. |
| |
The input value is similarly controlled via
P1.1; a logic 0 on this port pin opens the valve while a logic 1
cloeses it. Since, on reset, all 8051 port pins are at logic 1, both
valves will initially be closed. |
| |
A liquid level sensor is connected to the
external 1 interrupt (INT1). This sensor produces a logic 0 when the
liquid level is higher than max (red line). |
| |
Similarly, a level sensor connected to the
external 0 interrupt (INT0) produces a logic 0 when the liquid is lower
than min (green line). |
| |
 |
| |
|
| |
There is some process, controlled by the 8051,
that makes use of the liquid in this tank. Whenever the process
requires liquid from the tank, P1.0 is cleared and when enough liquid
has been taken from the tank the output valve is closed by setting
P1.0. However, for this example of the use of external interrupts, this
process is of no concern to us. We simply wish to write the code for
handling the two interrupts. |
| |
|
| |
When the liquid in the tank drops below the
minimum level, the INT0 line will go from HIGH to LOW: a negative edge.
The external 0 ISR should then open the input value in order to start
filling the tank. |
| |
When the level in the tank reaches the maximum,
the INT1 line will go from HIGH to LOW: a negative edge. The external 1
ISR should then close the input value in order to stop filling the tank. |
| |
|
| |
ORG 0
JMP main
ORG 0003H ; external interrupt 0 vector
CLR P1.1 ; open input valve
RETI
ORG 0013H ; external interrupt 1 vector
SETB P1.1 ; close input valve
RETI
ORG 0030H
|
| main: |
SETB IT0 ;
set external interrupt 0 as edge activated
SETB IT1 ; set external interrupt 1 as edge activated
SETB EX0 ; enable external interrupt 0
SETB EX1 ; enable external interrupt 1
SETB EA ; global interrupt enable
JB P3.2, skip ; if INT0-bar (P3.2) is logic 1 then the liquid level is
above min, therefore skip next instruction
CLR P1.1 ; if INT0-bar is logic 0 the liquid level is below min,
therefore turn open input valve
|
| skip: |
... ;
continue with main program (the process that makes use of the liquid in
the tank)
|
|
| |
|
| |
The ISRs are very simple. An external 0
interrupt occurs when the liquid level is too low, therefore the ISR at
vector 0003H simply opens the input valve. An external 1 interrupt
occurs when the liquid level is too high, therefore the ISR at vector
0013H simply closes the input valve. |
| |
It should be noted that the choice of applying
the min-liquid sensor to INT0-bar and the max-liquid sensor to INT1-bar
is completely arbitrary; the system would work exactly the same if we
swapped the sensors from one external interrupt pin to the other, as
long as we also swapped the ISRs. |
| |
The main program performs the
initialization. However, if the liquid level is already too low we will
never see a negative edge on INT0-bar (it is already at 0, and will
remain at 0, because the temperature is too low). We therefore need to
check to see if INT0-bar (which is at P3.2) is at logic 1. If it is
logic 1 it means the liquid level is not too low and we can jump to the
end of the initialization. However, if P3.3 is 0 then we must open the
input valve and then proceed with the main program. |
| |
|
| |
|
| |
|
| |
|
| |
|
| |
Copyright (c) 2005-2006 James Rogers
|