Download Real Interrupts-Microprocessor and Assembly Language Programming-Lecture Notes and more Study notes Microprocessor and Assembly Language Programming in PDF only on Docsity!
The same mechanism as discussed in the previous chapter is used for real
interrupts that are generated by external hardware. However there is a single
pin outside the processor called the INT pin that is used by external
hardware to generate interrupts. The detailed operation that happens outside
the process when an interrupt is generated is complex and only a simplified
view will be discussed here; the view that is relevant to an assembly language
programmer. There are many external devices that need the processor’s
attention like the keyboard, hard disk, floppy disk, sound card. All of them
need real time interrupts at some point in their operation. For example if a
program is busy in some calculations for three minutes the key strokes that
are hit meanwhile should not be wasted. Therefore when a key is pressed,
the INT signal is sent, an interrupt generated and the interrupt handler
stores the key for later use. Similarly when the printer is busy printing we
cannot send it more data. As soon as it gets free from the previous job it
interrupts the processor to inform that it is free now. There are many other
examples where the processor needs to be informed of an external event. If
the processor actively monitors all devices instead of being automatically
interrupted then it there won’t be any time to do meaningful work.
Since there are many devices generating interrupts and there is only one
pin going inside the processor and one pin cannot be technically derived by
more than one source a controller is used in between called the
Programmable Interrupt Controller (PIC). It has eight input signals and one
output signal. It assigns priorities to its eight input pins from 0 to 7 so that if
more than one interrupt comes at the same times, the highest priority one is
forwarded and the rest are held till that is serviced. The rest are forwarded
one by one according to priority after the highest priority one is completed.
The original IBM XT computer had one PIC so there were 8 possible interrupt
sources. However IBM AT and later computers have two PIC totaling 16
possible interrupt sources. They are arrange is a special cascade master
slave arrangement so that only one output signal comes towards the
processor. However we will concentrate on the first interrupt controller only.
The priority can be understood with the following example. Consider eight
parallel switches which are all closed and connected to form the output
signal. When a signal comes on one of the switches, it is passed on to the
output and this switch and all below it are opened so that no further signals
can pass through it. The higher priority switches are still closed and the
signal on them can be forwarded. When the processor signals that it is
finished with the processing the switches are closed again and any waiting
interrupts may be forwarded. The way the processor signals ending of the
interrupt service routine is by using a special mechanism discussed later.
The eight input signals to the PIC are called Interrupt Requests (IRQ). The
eight lines are called IRQ 0 to IRQ 7. These are the input lines of the 8451. †
For example IRQ 0 is derived by a timer device. The timer device keeps
† 8259 is the technical number of the PIC.
generating interrupts with a specified frequency. IRQ 1 is derived by the
keyboard when generates an interrupts when a key is pressed or released.
IRQ 2 is the cascading interrupt connected to the output of the second 8451
in the machine. IRQ 3 is connected to serial port COM 2 while IRQ 4 is
connected to serial port COM 1. IRQ 5 is used by the sound card or the
network card or the modem. An IRQ conflict means that two devices in the
system want to use the same IRQ line. IRQ 6 is used by the floppy disk drive
while IRQ 7 is used by the parallel port.
Each IRQ is mapped to a specific interrupt in the system. This is called the
IRQ to INT mapping. IRQ 0 to IRQ 7 are consecutively mapped on interrupts
8 to F. This mapping is done by the PIC and not the processor. The actual
mechanism fetches one instruction from the PIC whenever the INT pin is
signaled instead of the memory. We can program the PIC to generate a
different set of interrupts on the same interrupt requests. From the
perspective of an assembly language programmer an IRQ 0 is translated into
an INT 8 without any such instruction in the program and that’s all.
Therefore an IRQ 0, the highest priority interrupt, is generated by the timer
chip at a precise frequency and the handler at INT 8 is invoked which
updates the system time. A key press generates IRQ 1 and the INT 9 handler
is invoked which stores this key. To handler the timer and keyboard
interrupts one can replace the vectors corresponding to interrupt 8 and 9
respectively. For example if the timer interrupt is replaced and the floppy is
accessed by some program, the floppy motor and its light will remain on for
ever as in the normal case it is turned off by the timer interrupt after two
seconds in anticipation that another floppy access might be needed
otherwise the time motor takes to speed up will be needed again. ‡
We have seen that an interrupt request from a device enters the PIC as an
IRQ, from there it reaches the INT pin of the processor, the processor
receives the interrupt number from the PIC, generates the designated
interrupt, and finally the interrupt handler gain control and can do whatever
is desired. At the end of servicing the interrupt the handler should inform the
PIC that it is completed so that lower priority interrupts can be sent from the
PIC. This signal is called an End Of Interrupt (EOI) signal and is sent
through the I/O ports of the interrupt controller.
I/O PORTS
There are hundreds of peripheral devices in the system, PIC is one
example. The processor needs to communicate with them, give and take data
from them, otherwise their presence is meaningless. Memory has a totally
different purpose. It contains the program to be executed and its data. It
does not control any hardware. For communicating with peripheral devices
the processor uses I/O ports. There are only two operations with the external
world possible, read or write. Similarly with I/O ports the processor can read
or write an I/O port. When an I/O port is read or written to, the operation is
not as simple as it happens in memory. Some hardware changes it
functionality or performs some operation as a result.
IBM PC has separate memory address space and peripheral address space.
Some processors use memory mapped I/O in which case designated memory
cells work as ports for specific devices. In case of Intel a special pin on the
control bus signals whether the current read or write is from the memory
address space or from the peripheral address space. The same address and
data buses are used to select a port and to read or write data from that port.
However with I/O only the lower 16 bits of the address bus are used meaning
that there are a total of 65536 possible I/O ports. Now keyboard has special
‡ The programs discussed from now onwards in the book must be executed
in pure DOS and not in a DOS window so that we are in total control of the
PIC and other devices.
controller communicates with the processor. Keyboard is a collection of
labeled buttons and every button is designated a number (not the ASCII
code). This number is sent to the processor whenever the key is pressed.
From this number called the scan code the processor understands which key
was pressed. For each key the scan code comes twice, once for the key press
and once for the key release. Both are scan codes and differ in one bit only.
The lower seven bits contain the key number while the most significant bit is
clear in the press code and set in the release code. The IBM PC standard
gives a table of the scan codes of all keys.
If we press Shift-A resulting in a capital A on the screen, the controller has
sent the press code of Shift, the press code of A, the release code of A, the
release code of Shift and the interrupt handler has understood that this
sequence should result in the ASCII code of ‘A’. The ‘A’ key always produces
the same scan code whether or not shift is pressed. It is the interrupt
handler’s job to remember that the press code of Shift has come and release
code has not yet come and therefore to change the meaning of the following
key presses. Even the caps lock key works the same way.
An interesting thing is that the two shift keys on the left and right side of
the keyboard produce different scan codes. The standard way implemented
in BIOS is to treat that similarly. That’s why we always think of them as
identical. If we leave BIOS and talk directly with the hardware we can
differentiate between left and right shift keys with their scan code. Now this
scan code is available from the keyboard data port which is 60. The keyboard
generates IRQ 1 whenever a key is pressed so if we hook INT 9 and inside it
read port 60 we can tell which of the shift keys was hit. Our first program
will do precisely this. It will output an L if the left shift key was pressed and
R if the right one was pressed. The hooking method is the same as done in
the previous chapter.
Example
; differentiate left and right shift keys with scancodes [org 0x0100] jmp start
; keyboard interrupt service routine kbisr: push ax push es
mov ax, 0xb mov es, ax ; point es to video memory
in al, 0x60 ; read a char from keyboard port cmp al, 0x2a ; is the key left shift jne nextcmp ; no, try next comparison
mov byte [es:0], 'L' ; yes, print L at top left jmp nomatch ; leave interrupt routine
nextcmp: cmp al, 0x36 ; is the key right shift jne nomatch ; no, leave interrupt routine
mov byte [es:0], 'R' ; yes, print R at top left
nomatch: mov al, 0x out 0x20, al ; send EOI to PIC
pop es pop ax iret
start: xor ax, ax mov es, ax ; point es to IVT base cli ; disable interrupts mov word [es:94], kbisr ; store offset at n mov [es:94+2], cs ; store segment at n4+ sti ; enable interrupts
038 l1: jmp l1 ; infinite loop
033-036 CLI clears the interrupt flag to disable the interrupt system
completely. The processor closes its ears and does not care about
the state of the INT pin. Interrupt hooking is done in two
instructions, placing the segment and placing the offset. If an
interrupt comes in between and the vector is in an indeterminate
state, the system will go to a junk address and eventually crash. So
we stop all interruptions while changing a real time interrupt vector.
We set the interrupt flag afterwards to renewable interrupts.
038 The program hangs in an infinite loop. The only activity can be
caused by a real time interrupt. The kbisr routine is not called from
anywhere; it is only automatically invoked as a result of IRQ 1.
When the program is executed the left and right shift keys can be
distinguished with the L or R on the screen. As no action was taken for the
rest of the keys, they are effectively disabled and the computer has to be
rebooted. To check that the keyboard is actually disabled we change the
program and add the INT 16 service 0 at the end to wait for an Esc key press.
As soon as Esc is pressed we want to terminate our program.
Example
; attempt to terminate program with Esc that hooks keyboard interrupt [org 0x0100] jmp start
;;;;; COPY LINES 005-029 FROM EXAMPLE 9.2 (kbisr) ;;;;;
start: xor ax, ax mov es, ax ; point es to IVT base cli ; disable interrupts mov word [es:94], kbisr ; store offset at n mov [es:94+2], cs ; store segment at n4+ sti ; enable interrupts
l1: mov ah, 0 ; service 0 – get keystroke int 0x16 ; call BIOS keyboard service
cmp al, 27 ; is the Esc key pressed jne l1 ; if no, check for next key
mov ax, 0x4c00 ; terminate program int 0x
When the program is executed the behavior is same. Esc does not work.
This is because the original IRQ 1 handler was written by BIOS that read the
scan code, converted into an ASCII code and stored in the keyboard buffer.
The BIOS INT 16 read the key from there and gives in AL. When we hooked
the keyboard interrupt BIOS is no longer in control, it has no information, it
will always see the empty buffer and INT 16 will never return.
Interrupt Chaining
We can transfer control to the original BIOS ISR in the end of our routine.
This way the normal functioning of INT 16 can work as well. We can retrieve
the address of the BIOS routine by saving the values in vector 9 before
hooking our routine. In the end of our routine we will jump to this address
using a special indirect form of the JMP FAR instruction.
Example
memory as a result. The routine is still in memory and functioning but the
memory is free according to DOS. As soon as we load EDIT the same memory
is allocated to EDIT and our routine as overwritten. Now when a key is
pressed our routine’s address is in the vector but at that address some new
code is placed that is not intended to be an interrupt handler. That may be
data or some part of the EDIT program. This results in crashing the
computer.
Unhooking Interrupt
We now add the interrupt restoring part to our program. This code resets
the interrupt vector to the value it had before the start of our program.
Example
; terminate program with Esc that hooks keyboard interrupt [org 0x100] jmp start
oldisr: dd 0 ; space for saving old isr
;;;;; COPY LINES 005-029 FROM EXAMPLE 9.4 (kbisr) ;;;;;
start: xor ax, ax mov es, ax ; point es to IVT base mov ax, [es:94] mov [oldisr], ax ; save offset of old routine mov ax, [es:94+2] mov [oldisr+2], ax ; save segment of old routine cli ; disable interrupts mov word [es:94], kbisr ; store offset at n mov [es:94+2], cs ; store segment at n4+ sti ; enable interrupts
l1: mov ah, 0 ; service 0 – get keystroke int 0x16 ; call BIOS keyboard service
cmp al, 27 ; is the Esc key pressed jne l1 ; if no, check for next key
mov ax, [oldisr] ; read old offset in ax mov bx, [oldisr+2] ; read old segment in bx cli ; disable interrupts mov [es:94], ax ; restore old offset from ax mov [es:94+2], bx ; restore old segment from bx sti ; enable interrupts
mov ax, 0x4c00 ; terminate program int 0x
. TERMINATE AND STAY RESIDENT
We change the display to show L only while the left shift is pressed and R
only while the right shift is pressed to show the use of the release codes. We
also changed that shift keys are not forwarded to BIOS. The effect will be
visible with A and Shift-A both producing small ‘a’ but caps lock will work.
There is one major difference from all the programs we have been writing
till now. The termination is done using INT 21 service 31 instead of INT 21
service 4C. The effect is that even after termination the program is there and
is legally there.
Example
; TSR to show status of shift keys on top left of screen [org 0x0100] jmp start
oldisr: dd 0 ; space for saving old isr
; keyboard interrupt service routine kbisr: push ax push es
mov ax, 0xb mov es, ax ; point es to video memory
in al, 0x60 ; read a char from keyboard port cmp al, 0x2a ; has the left shift pressed jne nextcmp ; no, try next comparison
mov byte [es:0], 'L' ; yes, print L at first column jmp exit ; leave interrupt routine
nextcmp: cmp al, 0x36 ; has the right shift pressed jne nextcmp2 ; no, try next comparison
mov byte [es:0], 'R' ; yes, print R at second column jmp exit ; leave interrupt routine
nextcmp2: cmp al, 0xaa ; has the left shift released jne nextcmp3 ; no, try next comparison
mov byte [es:0], ' ' ; yes, clear the first column jmp exit ; leave interrupt routine
nextcmp3: cmp al, 0xb6 ; has the right shift released jne nomatch ; no, chain to old ISR
mov byte [es:2], ' ' ; yes, clear the second column jmp exit ; leave interrupt routine
nomatch: pop es pop ax jmp far [cs:oldisr] ; call the original ISR
exit: mov al, 0x out 0x20, al ; send EOI to PIC
pop es pop ax iret ; return from interrupt
start: xor ax, ax mov es, ax ; point es to IVT base mov ax, [es:94] mov [oldisr], ax ; save offset of old routine mov ax, [es:94+2] mov [oldisr+2], ax ; save segment of old routine cli ; disable interrupts mov word [es:94], kbisr ; store offset at n mov [es:94+2], cs ; store segment at n4+ sti ; enable interrupts
mov dx, start ; end of resident portion add dx, 15 ; round up to next para mov cl, 4 shr dx, cl ; number of paras mov ax, 0x3100 ; terminate and stay resident int 0x
When this program is executed the command prompt immediately comes.
DIR can be seen. EDIT can run and keypresses do not result in a crash. And
with all that left and right shift keys shown L and R on top left of the screen
while they are pressed but the shift keys do not work as usual since we did
not forwarded the key to BIOS. This is selective chaining.
This command displays all currently loaded drivers and the current state
of memory. We will be able to see our program in the list of DOS drivers.
PROGRAMMABLE INTERVAL TIMER
Another very important peripheral device is the Programmable Interval
Timer (PIT), the chip numbered 8254. This chip has a precise input
frequency of 1.19318 MHz. This frequency is fixed regardless of the processor
clock. Inside the chip is a 16bit divisor which divides this input frequency
and the output is connected to the IRQ 0 line of the PIC. The special number
0 if placed in the divisor means a divisor of 65536 and not 0. The standard
divisor is 0 unless we change it. Therefore by default IRQ 0 is generated
1193180/65536=18.2 times per second. This is called the timer tick. There is
an interval of about 55ms between two timer ticks. The system time is
maintained with the timer interrupt. This is the highest priority interrupt
and breaks whatever is executing. Time can be maintained with this
interrupt as this frequency is very precise and is part of the IBM standard.
When writing a TSR we give control back to DOS so TSR activation,
reactivation and action is solely interrupt based, whether this is a hardware
interrupt or a software one. Control is never given back; it must be caught,
just like we caught control by hooking the keyboard interrupt. Our next
example will hook the timer interrupt and display a tick count on the screen.
Example
; display a tick count on the top right of screen [org 0x0100] jmp start
tickcount: dw 0
; subroutine to print a number at top left of screen ; takes the number to be printed as its parameter printnum: push bp mov bp, sp push es push ax push bx push cx push dx push di
mov ax, 0xb mov es, ax ; point es to video base mov ax, [bp+4] ; load number in ax mov bx, 10 ; use base 10 for division mov cx, 0 ; initialize count of digits
nextdigit: mov dx, 0 ; zero upper half of dividend div bx ; divide by 10 add dl, 0x30 ; convert digit into ascii value push dx ; save ascii value on stack inc cx ; increment count of values cmp ax, 0 ; is the quotient zero jnz nextdigit ; if no divide it again
mov di, 140 ; point di to 70th column
nextpos: pop dx ; remove a digit from the stack mov dh, 0x07 ; use normal attribute mov [es:di], dx ; print char on screen add di, 2 ; move to next screen location loop nextpos ; repeat for all digits on stack
pop di pop dx pop cx pop bx pop ax
pop es pop bp ret 2
; timer interrupt service routine timer: push ax
inc word [cs:tickcount]; increment tick count push word [cs:tickcount] call printnum ; print tick count
mov al, 0x out 0x20, al ; end of interrupt
pop ax iret ; return from interrupt
start: xor ax, ax mov es, ax ; point es to IVT base cli ; disable interrupts mov word [es:84], timer; store offset at n mov [es:84+2], cs ; store segment at n4+ sti ; enable interrupts
mov dx, start ; end of resident portion add dx, 15 ; round up to next para mov cl, 4 shr dx, cl ; number of paras mov ax, 0x3100 ; terminate and stay resident int 0x
When we execute the program the counter starts on the screen. Whatever
we do, take directory, open EDIT, the debugger etc. the counter remains
running on the screen. No one is giving control to the program; the program
is getting executed as a result of timer generating INT 8 after every 55ms.
Our next example will hook both the keyboard and timer interrupts. When
the shift key is pressed the tick count starts incrementing and as soon as the
shift key is released the tick count stops. Both interrupt handlers are
communicating through a common variable. The keyboard interrupt sets this
variable while the timer interrupts modifies its behavior according to this
variable.
Example
; display a tick count while the left shift key is down [org 0x0100] jmp start
seconds: dw 0 timerflag: dw 0 oldkb: dd 0
;;;;; COPY LINES 007-047 FROM EXAMPLE 9.7 (printnum) ;;;;;
; keyboard interrupt service routine kbisr: push ax
in al, 0x60 ; read char from keyboard port cmp al, 0x2a ; has the left shift pressed jne nextcmp ; no, try next comparison
cmp word [cs:timerflag], 1; is the flag already set je exit ; yes, leave the ISR
mov word [cs:timerflag], 1; set flag to start printing jmp exit ; leave the ISR
nextcmp: cmp al, 0xaa ; has the left shift released jne nomatch ; no, chain to old ISR
mov word [cs:timerflag], 0; reset flag to stop printing
ports through which the processor communicates with the device connected
to the parallel port.
The parallel port connector is a 25pin connector called DB-25. Different
pins of this connector have different meanings. Some are meaningful only
with the printer **^. This is a bidirectional port so there are some pins to take
data from the processor to the parallel port and others to take data from the
parallel port to the processor. Important pins for our use are the data pins
from pin 2 to pin 9 that take data from the processor to the device. Pin 10,
the ACK pin, is normally used by the printer to acknowledge the receipt of
data and show the willingness to receive more data. Signaling this pin
generates IRQ 7 if enabled in the PIC and in the parallel port controller. Pin
18-25 are ground and must be connected to the external circuit ground to
provide the common reference point otherwise they won’t understand each
other voltage levels. Like the datum point in a graph this is the datum point
of an electrical circuit. The remaining pins are not of our concern in these
examples.
This is the external view of the parallel port. The processor cannot see
these pins. The processor uses the I/O ports of the parallel port controller.
The first parallel port LPT1 ††^ has ports designated from 378 to 37A. The first
port 378 is the data port. If we use the OUT instruction on this port, 1 bits
result in a 5V signal on the corresponding pin and a 0 bits result in a 0V
signal on the corresponding pin.
Port 37A is the control port. Our interest is with bit 4 of this port which
enables the IRQ 7 triggering by the ACK pin. We have attached a circuit that
connects 8 LEDs with the parallel port pins. The following examples sends
the scancode of the key pressed to the parallel port so that it is visible on
LEDs.
Example
; show scancode on external LEDs connected through parallel port [org 0x0100] jmp start
oldisr: dd 0 ; space for saving old ISR
; keyboard interrupt service routine kbisr: push ax push dx
in al, 0x60 ; read char from keyboard port mov dx, 0x out dx, al ; write char to parallel port
pop ax pop dx jmp far [cs:oldisr] ; call original ISR
start: xor ax, ax mov es, ax ; point es to IVT base mov ax, [es:94] mov [oldisr], ax ; save offset of old routine mov ax, [es:94+2] mov [oldisr+2], ax ; save segment of old routine cli ; disable interrupts mov word [es:94], kbisr ; store offset at n mov [es:94+2], cs ; store segment at n4+ sti ; enable interrupts
mov dx, start ; end of resident portion add dx, 15 ; round up to next para
** The parallel port is most commonly used with the printer. However some
new printers have started using the USB port.
†† Older computer had more than one parallel port named LPT2 and having
ports from 278-27A.
mov cl, 4 shr dx, cl ; number of paras mov ax, 0x3100 ; terminate and stay resident int 0x
The following example uses the same LED circuit and sends data such that
LEDs switch on and off turn by turn so that it looks like light is moving back
and forth.
Example
; show lights moving back and forth on external LEDs [org 0x0100] jmp start
signal: db 1 ; current state of lights direction: db 0 ; current direction of motion
; timer interrupt service routine timer: push ax push dx push ds
push cs pop ds ; initialize ds to data segment
cmp byte [direction], 1; are moving in right direction je moveright ; yes, go to shift right code
shl byte [signal], 1 ; shift left state of lights jnc output ; no jump to change direction
mov byte [direction], 1; change direction to right mov byte [signal], 0x80; turn on left most light jmp output ; proceed to send signal
moveright: shr byte [signal], 1 ; shift right state of lights jnc output ; no jump to change direction
mov byte [direction], 0; change direction to left mov byte [signal], 1 ; turn on right most light
output: mov al, [signal] ; load lights state in al mov dx, 0x378 ; parallel port data port out dx, al ; send light state of port
mov al, 0x out 0x20, al ; send EOI on PIC
pop ds pop dx pop ax iret ; return from interrupt
start: xor ax, ax mov es, ax ; point es to IVT base cli ; disable interrupts mov word [es:84], timer ; store offset at n mov [es:84+2], cs ; store segment at n4+ sti ; enable interrupts
mov dx, start ; end of resident portion add dx, 15 ; round up to next para mov cl, 4 shr dx, cl ; number of paras mov ax, 0x3100 ; terminate and stay resident int 0x
We will now use the parallel port to control a slightly complicated circuit.
This time we will also use the parallel port interrupt. We are using a 220 V
bulb with AC input. AC current is 50Hz sine wave. We have made our circuit
such that it triggers the parallel port interrupt whenever the since wave
start: xor ax, ax mov es, ax ; point es to IVT base mov ax, [es:0x084] mov [oldtimer], ax ; save offset of old routine mov ax, [es:0x084+2] mov [oldtimer+2], ax ; save segment of old routine cli ; disable interrupts mov word [es:0x084], timer ; store offset at n mov [es:0x084+2], cs ; store segment at n4+ mov word [es:0x0F4], parallel ; store offset at n mov [es:0x0F4+2], cs ; store segment at n4+ sti ; enable interrupts
mov dx, 0x37A in al, dx ; parallel port control register or al, 0x10 ; turn interrupt enable bit on out dx, al ; write back register
in al, 0x21 ; read interrupt mask register and al, 0x7F ; enable IRQ7 for parallel port out 0x21, al ; write back register
recheck: cmp byte [stop], 1 ; is the termination flag set jne recheck ; no, check again
mov dx, 0x37A in al, dx ; parallel port control register and al, 0xEF ; turn interrupt enable bit off out dx, al ; write back register
in al, 0x21 ; read interrupt mask register or al, 0x80 ; disable IRQ7 for parallel port out 0x21, al ; write back regsiter
cli ; disable interrupts mov ax, [oldtimer] ; read old timer ISR offset mov [es:0x084], ax ; restore old timer ISR offset mov ax, [oldtimer+2] ; read old timer ISR segment mov [es:0x084+2], ax ; restore old timer ISR segment sti ; enable interrupts
mov ax, 0x4c00 ; terminate program int 0x
The next example is simply the opposite of the previous. It slowly turns the
bulb off from maximum glow to no glow.
Example
; slowly turn off a bulb by gradually decreasing the power provided [org 0x0100] jmp start
flag: db 0 ; next time turn on or turn off stop: db 0 ; flag to terminate the program divider: dw 0 ; divider for maximum intensity oldtimer: dd 0 ; space for saving old isr
;;;;; COPY LINES 009-036 FROM EXAMPLE 9.11 (timer) ;;;;;
; parallel port interrupt service routine parallel: push ax
mov al, 0x30 ; set timer to one shot mode out 0x43, al
cmp word [cs:divider], 11000; current divisor is 11000 je stopit ; yes, stop
add word [cs:divider], 10; increase the divisor by 10 mov ax, [cs:divider] out 0x40, al ; load divisor LSB in timer mov al, ah out 0x40, al ; load divisor MSB in timer
mov byte [cs:flag], 1 ; flag next timer to switch on
mov al, 0x out 0x20, al ; send EOI to PIC pop ax iret ; return from interrupt
stopit: mov byte [stop], 1 ; flag to terminate the program mov al, 0x out 0x20, al ; send EOI to PIC pop ax iret ; return from interrupt
start: xor ax, ax mov es, ax ; point es to IVT base mov ax, [es:0x084] mov [oldtimer], ax ; save offset of old routine mov ax, [es:0x084+2] mov [oldtimer+2], ax ; save segment of old routine cli ; disable interrupts mov word [es:0x084], timer ; store offset at n mov [es:0x084+2], cs ; store segment at n4+ mov word [es:0x0F4], parallel ; store offset at n mov [es:0x0F4+2], cs ; store segment at n4+ sti ; enable interrupts
mov dx, 0x37A in al, dx ; parallel port control register or al, 0x10 ; turn interrupt enable bit on out dx, al ; write back register
in al, 0x21 ; read interrupt mask register and al, 0x7F ; enable IRQ7 for parallel port out 0x21, al ; write back register
recheck: cmp byte [stop], 1 ; is the termination flag set jne recheck ; no, check again
mov dx, 0x37A in al, dx ; parallel port control register and al, 0xEF ; turn interrupt enable bit off out dx, al ; write back register
in al, 0x21 ; read interrupt mask register or al, 0x80 ; disable IRQ7 for parallel port out 0x21, al ; write back regsiter
cli ; disable interrupts mov ax, [oldtimer] ; read old timer ISR offset mov [es:0x084], ax ; restore old timer ISR offset mov ax, [oldtimer+2] ; read old timer ISR segment mov [es:0x084+2], ax ; restore old timer ISR segment sti ; enable interrupts
mov ax, 0x4c00 ; terminate program int 0x
This example is a mix of the previous two. Here we can increase the bulb
intensity with F11 and decrease it with F12.
Example
; control external bulb intensity with F11 and F [org 0x0100] jmp start
flag: db 0 ; next time turn on or turn off divider: dw 100 ; initial timer divider oldkb: dd 0 ; space for saving old ISR
;;;;; COPY LINES 009-036 FROM EXAMPLE 9.11 (timer) ;;;;;
; keyboard interrupt service routine kbisr: push ax