Time Delays / Function 2CH INT 21H

advertisement
ELEC 242 Time Delay Procedure
There are many occasions where we wish to time events. If we are using a personal computer,
we have a number of ways to do this. The 8088/8086 computer had a Programmable Interval
Timer like the 8253/54 installed. One of the three count down timer could be initialized to a
count and then would count down to zero. The system clock was applied to the clock input
terminal, and this clock controlled the rate at which the PIT counts down to zero. When the
count reached zero, it would generate an interrupt to the processor.
There is an easier way to create time delays since all computer beginning with the 80286 have a
real time clock, and MS-DOS has at least two functions that will read the date and time from
the clock. One is Function 2CH INT 21h, and the other is function 02H INT 1AH. Since the
output of both functions is the same, it really doesn’t matter which one we use. Therefore, we
will use Function 2CH INT 21H.
Arithmetic Instructions for Multiply and Divide
MUL
In order to perform the time delay routine, we need to have knowledge of these instructions and
their requirements. The multiply instruction multiplies an 8-bit, 16-bit, or 32-bit operand in AL,
AX, or EAX by a register or memory multiplier. The operand cannot be immediate. The
instruction syntax is:
mul
mul
register
memory
The following shows the source and destination registers for various multiply instructions.
Multiplicand
Multiplier
Result
AL
8-bit
AX
AX
16-bit
DX:AX
EAX
32-bit
EDX:EAX
Example: Multiply 20 by 4 using a register operand:
mov
al,20
mov
bl,4
mul
bl
; The product is located in AX and is 0050H
Example: Multiply 2000 by 1000 using a memory operand:
num1
num2
.data
dw
2000
dw
1000
mov
ax,num1
mul
num2
; The product is located in DX:AX
; DX = 001EH and AX = 8480H
Note: To multiply two memory operands, one must be placed in a register.
Example: Multiply 75 000 by 2 500 000 using a memory operand:
num1
num2
.data
dw
75 000
dw
2 500 000
mov
eax,num1
mul
num2
; The product is located in EDX:EAX
; EDX = 0000 002BH and EAX = A7DE F300H
Page 1
ELEC 242 Time Delay Procedure
DIV
The divide instruction divides an 8-bit, 16-bit, or 32-bit operand in, AX, or EAX by a register
or memory dividend. The operand cannot be immediate. The instruction format is:
div
div
register
memory
The following shows the source and destination registers for various divide instructions.
Dividend
Divider
Quotient
Remainder
AX
8-bit
AL
AH
DX:AX
16-bit
AX
DX
EDX:EAX
32-bit
EAX
EDX
E
Example: Divide 21 by 4 using a register operand:
mov
ax,21
mov
bl,4
div
bl
; The quotient is located in AL and is 0005H
; The remainder is located in AH and is 0001H
Example: Divide 2000 by 1000 using a memory operand:
num1
num2
.data
dw
2000
dw
1000
mov
dx,0
mov
ax,num1
div
num2
; The remainder:quotient is located in DX:AX
; DX = 0000H and AX = 0002H
Note: To multiply two memory operands, one must be placed in a register. Example: Divide 2
505 000 by 50 000 using a memory operand:
num1
num2
.data
dw
50 000
dw
2 505 000
mov
edx,0
mov
eax,num2
div
num1
; The remainder:quotient is located in EDX:EAX
; EDX = 0000 0001H and EAX = 0000 0032H
REQUIREMENTS FOR FUNCTION 2CH INT 21H READ SYSTEM CLOCK
We will be using BIOS to read the system clock. This function returns the current contents of
the system clock into the CX and DX registers. Therefore, if these registers contain data that
will be needed after the time delay, we should PUSH CX and DX onto the Stack before the
INT 21H, and POP them after the return.
The contents of the clock is the number of hours, minutes, seconds, and 1/100ths of seconds since
midnight.
The Read System Clock function works as follows:
On Entry:
AH <= 02Ch ;reads the system clock on execution of INT 21h
Page 2
ELEC 242 Time Delay Procedure
On Exit:
CH has hour
; in UTC format from 00 to 23
CL minutes
; as a value from 00 to 59
DH has the seconds
; as a value from 00 to 59
DL 1/100 s
; as a value from 00 to 99
As you can see, we can use this function to assemble the time in a format like 18:45 34 89.
Since we wish to write a program that generates a time delay, we could read the current time
and keep reading the clock until the desired time has been reached. For instance, if we want a 5
second time delay, read the clock and add five seconds to the current time. Now enter a loop
that reads the current time, and compares it to the exit time. We say in the loop until the current
time is >= the initial time plus five seconds. In our case, we would ignore the hundredths of
seconds.
However, we would still have to compare the hours, minutes and seconds to create a more
flexible time delay, one that is easily modified for timing intervals of hours.
However, we will do the time delay as follows:
1. Read the RTC (Real Time Clock)
2. Convert the hours to seconds (from the beginning of the day) Note the result can be as
large as a doubleword (23 hours times 3600 seconds)
3. Convert the minutes to seconds
4. Add the hours in seconds to the minutes in seconds and the seconds
5. Add the time interval in seconds
6. Store it in a memory buffer
7. Enter a loop that repeats steps 1 through 4
8. Compare the current time to the start time (in the storage buffer)
9. Jump to step 7 while the value in seconds for the current time is less than the end time.
NOTE: Since we are dealing with double word size data, we need to use an 80386 or higher
processor with the 386 mode enabled. If we use AX for the calculations and compares, the
register would be accessed as follows:
mov
mov
cmp
eax, hour
[EndTime],eax
eax,[EndTime]
80x86 Time Delay – Reading the system clock
This procedure uses 32-bit instructions. Therefore, we need to instruct the assembler to use
80386 instructions. To do this, add the .386 directive after the model directive as follows:
.model small
.386
We will be using BIOS to read the system clock. It works as follows:
On Entry:
AH <= 02Ch ;reads the system clock on INT 21h
On Exit:
CH has hour
CL minutes
DH has the seconds
DL 1/100 s
Page 3
ELEC 242 Time Delay Procedure
Since we will need access to these registers in a loop while we still need the data, we will
require storage to hold the time data. Therefore we need to setup storage in the .data area. Since
we will not use the 1/100 s data in this program, we do not create storage or save this data. The
names we use for storage can be anything we wish, but it is a good idea to make them selfexplanatory.
Hours
Minutes
Seconds
.DATA
DB
?
DB
?
DB
?
On return from INT 21H, we would store the data as follows:
; [Hours]
<= CH
; [Minutes] <= CL
; [Seconds] <= DH
Also, we will need storage to hold the current time and the time we wish to end. Since this may
be 32-bit data, storage will be on a double word boundary. Once again, the names we use do
not matter. You may use your own as long as you make sure to use the same names in your
procedure. Here is one way to do this.
In the .data area, enter the following:
T_wait
T_now
DD
DD
?
?
We will see the specifics of how these data are used shortly.
Page 4
ELEC 242 Time Delay Procedure
80x86 TIME DELAY
Since a time delay is a common function, we will write it as a procedure. This allows us the
ability to copy and paste to other programs. We could also make it a public procedure,
assemble and create an object file, and link to it. This is something we will discuss in later
programs.
While we could write this as a single procedure, I chose to write it as two procedures. Since
Time_Delay will read the system clock multiple times, I felt it was more effective to use nested
procedures as opposed to nested loops. The two procedures are named Time_Delay and
Read_Time. I think that you can figure out what these two procedures accomplish based on
their names. We will discuss both in detail.
DISCUSSION
THE READ_TIME PROCEDURE
Read_Time reads the system clock and returns hours, minutes, seconds, and 1/100’s of
seconds. We will discard the hundredths of a second value. We previously described which
registers return the data, and how to save the data in storage. How can we process this data
easily? I mentioned that we would consider comparing hours, minutes, and seconds from the
start time to the current time using multiple IF – THEN – ELSE statements implemented in
assembler.
What we will do instead is to convert the time to the number of seconds since the beginning of
the day. One problem, which we will not consider in our program, is day rollover (time delays
that occur over midnight). This could be implemented without too much trouble. However, we
are writing a 5 second time delay, so it is doubtful that this would cause a problem. There is
additional processing that we could perform to eliminate this problem. We do not have to worry
about an AM – PM rollover since the computer clock is implemented in a 24-hour clock format
(00 = midnight and 12 = noon).
To simplify the program, we will convert the hour/minute/second data returned by the BIOS
routine to seconds in Read_Time. So how is this accomplished?
1.
Take the hours data and multiply it by 3600 (there are 3600 seconds in 1 hour)
2.
Take the minutes data and multiply by 60 (60 seconds per hour)
3.
Add the hours and minutes data with the seconds data and we have the number of
seconds since midnight.
What size storage do we need? The hours data will tell us what size data storage to declare.
Since there are 24*3600-1 or 86399 seconds per day, we must use double-word size data. I
would place this data at the beginning of the data segment to avoid potential memory data
boundary glitches. Place and word or byte sized data after.
Earlier, we discussed how to move data as double word size using registers named EAX, EBX,
ECX, and EDX.
Also, we will be using the AH, CX, and DX registers, so remember to save them on the Stack.
Read_Time Procedure requirements
On Entry:
The AH register is loaded with the BIOS function 2Ch and INT 21H is executed
Page 5
ELEC 242 Time Delay Procedure
On Exit:
CH has the Hour data
CL has the Minute data
DH has the Second data
DL has the 1/100s second data (which is not used in this procedure)
The contents of CL is copied into the Minutes storage buffer in the Data Segment
The contents of DH is copied into the Seconds storage buffer in the Data Segment
The number of seconds since midnight has been copied into the storage buffer
T_now and is also in EAX
Note, although we have reserved storage for the Hours data, we did not preserve it since we
used it immediately. However, we could save this data if we wished to modify the procedure to
handle a time delay that rolled over midnight.
The procedure requires four storage buffers in the data segment. These are declared as follows:
T_now
Hours
Minutes
Seconds
DD
DB
DB
DB
?
?
?
?
The basic (first draft) algorithm for Read_Time is:
1.
Read the system clock using function 2Ch and INT 21h
2.
Convert the current time to seconds since the beginning of the day on a double-word
boundary. Assume this routine will not incur a day rollover.
3.
Place the current time converted to seconds in T_now storage buffer in the data segment
(a DD size storage element)
4.
RETurn to the calling module or procedure Step 2 in the algorithm contains three
separate parts, which were mentioned earlier.
Further refinement yields the complete algorithm follows:
Read_Time proc
1.
Read the system clock using function 2Ch and INT 21h
2.
Convert the current time to seconds since the beginning of the day on a doubleword boundary as follows:
3.
Clear EAX ;There are many ways to do this, but the easiest is to XOR EAX with
itself
4.
Copy Hours data from register CH into AL
5.
Save the minutes data from CL to storage (Minutes) and the seconds data from
DH to storage (Seconds)
6.
Place 3600 in EBX ;there are 3600s/hr
7.
Multiply the number of hours data in EAX with EBX, the number of seconds per
hour
Note: MUL EBX will place the low doubleword of the result in EAX, and the high
doubleword of the result in EDX. However, EDX will always be zero for this
subroutine since the maximum value for hours is 23. Therefore we may ignore
EDX
8.
Copy the hours data from EAX to T_now
9.
Clear EAX
Page 6
ELEC 242 Time Delay Procedure
10.
11.
12.
13.
Copy the data from the Minutes storage into AL
Place the value 60 into EBX ;60s/minute
Multiply EAX, the minutes data with EBX, the number of seconds per minute
Add the data buffer T_now to EAX; we are adding the hours as seconds to minutes
as seconds
14. Copy the hours + minutes data from EAX to T_now
15. Clear EAX
16. Copy the data from the Seconds storage into AL
17. Add the data buffer T_now to EAX; now EAX contains the number of seconds in
the day since midnight
18. Copy the current time in seconds from EAX into T_now
19. RETurn to the calling module or procedure
Read_Now endp
This is how we read the system clock, and convert the current time into the number of seconds
since midnight. The complete algorithm is contained in the 18 steps above. Remember, the
current time is passed out in the data storage area using the name T_now, and is stored as a
Double Word size data. This means that whenever we use this data, it must be placed in an
extended register (32-bits), and we must use the 386 assembler directive.
THE TIME DELAY PROCEDURE
This procedure will enter a loop that waits for about 5 seconds or any time based on the data
stored in the data segment buffer named T_wait, and returns to the calling module or
procedure. This method requires T_wait to be initialized. If we wish, we could use standard
input to accept the time delay from the user. In the latter case, there are no specific data
requirements that must be initialized for entry. Only T_wait, a double word sized data buffer, is
required as storage in the data segment. However, this procedure will call another procedure
that has additional storage requirements. However, these parameters are not passed between the
Time_Delay procedure and the calling procedure or module. The parameter T_now is passed
between Time_Delay and Read_Time.
Time_Delay Procedure requirements
On Entry:
Wait_time is byte-sized data (max delay is 255 s) initialized to the number of
seconds required for the time delay and passed into the procedure.
T_Wait must be created as a storage buffer in the data segment, and this is passed
between Time_Delay and Read_Time
On Exit:
A delay has been executed.
No data are passed to the calling module or procedure.
The procedure requires six storage buffers in the data segment. Of these four are declared for
Read_Time and two more are required for Time_Delay. All six buffers might be declared as
follows:
T_wait
T_now
Hours
Minutes
DD
DD
DB
DB
?
?
?
?
Page 7
ELEC 242 Time Delay Procedure
Seconds
Wait_time
DB
DB
?
?
Time Delay ALGORITHM
Time_Delay proc
1.
PUSH AX, CX, and DX on the stack
2.
Call the subroutine Read_Time to get the current time in seconds since midnight
On return, EAX contains the Current Time
3.
Clear ECX
4.
Add Wait_Time into CL
5.
Add EAX and ECX
6.
Copy EAX into T_wait
7.
Enter a loop
8.
Call Read_Time to get the current time in seconds since midnight
9.
Compare EAX to T_wait
10. Loop to step 7 while current time (EAX) is less than end time (T_wait)
11. POP DX, CX, and AX from the stack
12. On loop exit, return to calling module or procedure
Time_Delay endp
Page 8
Download