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