Displaying Time in Assembly
Hello im trying to display the actual time hours/minutes/seconds this is my code sample:
MOV AH, 2Ch
INT 21h
MOV AH, 0Eh
MOV AL, CH
INT 10h
MOV AL, 3Ah
INT 10h
MOV AL, CL
INT 10h
MOV AL, 3Ah
INT 10h
MOV AL, DH
INT 10h
ret
Here you can se what the console is displaying
See the x86 tag wiki for the instruction set reference manual, and many good links to reference material and tutorials.
It takes enough code to split up an integer into ASCII digits that you should factor it out into a function.
This is an optimized and bugfixed version of @hobbs's print2Digits function. (I also bugfixed the version in his answer, so it's correct too, but left the optimizations for this one).
print2Digits:
;; input in AL (0-99). (Or preferably already zero-extended to AX so we can omit CBW)
;; clobbers AX and DX
cbw ; zero AH. Sign-extending AL does the job because AL is only allowed to be 0-99.
mov dl, 10
div dl ; quotient in AL(first (high) digit), remainder in AH(second (low) digit)
add ax, 0x3030 ; add '0' to al and ah at the same time.
mov dl, ah ; save the 2nd digit
mov ah, 0x0E ; BIOS call #: print single character
int 0x10 ; print high digit first. Doesn't clobber anything, so AH still holds 0x0E after
mov al, dl
int 0x10 ; print the low digit 2nd
ret
Since we used div
to split an integer into two base10 digits, we need ah
to be zero. i.e. for the dividend to be in AX, not just AL with possible garbage in AH. We could save the cbw
or mov ah,0
if the caller did movzx ax, ch
or something to zero ah
.
(Except that 8086 doesn't have movzx
, so you'd actually want xor ax,ax
/ mov al, ch
.)
There's a DOS system call for printing a whole string, so you could store characters into a small buffer and print them all at once, like I do in this AMD64 Linux FizzBuzz. See also How do I print an integer in Assembly Level Programming without printf from the c library? for a more general int->string in a buffer function, or other multi-digit number links in the x86 tag wiki
It's also possible to use aam
to divide AL (instead of AX) by 10, avoiding the need to zero AH first. It's slightly faster than div r8
on current Intel and AMD CPUs. However, it puts the results in the opposite registers from div
, which means extra instructions after the aam
. This balances out the saving on the mov dl, 10
and cbw
.
print2Digits:
;; input in AL (0-99). (Ignores AH because we use AAM instead of div)
;; clobbers AX and DX
aam ; like `div` by 10, but with the outputs reversed, and input from AL only
;; quotient in AH (high digit), remainder in AL(low digit). (Opposite to div)
add ax, 0x3030 ; add '0' to al and ah at the same time.
mov dl, al ; save the low digit
mov al, ah ; print high digit first
mov ah, 0x0E ; BIOS call #: print single character
int 0x10 ; print first digit. Doesn't clobber anything, so AH still holds 0x0E after
mov al, dl
int 0x10 ; print second digit
ret
Even if we wanted to store to a string (and make one call to a print-string function or system call), we'd have to swap al and ah before storing AX to memory (e.g. xchg al,ah
, or more efficiently on modern hardware but requiring 186: rol ax,8
). div
produces them in the right order inside AX.
For 386 where 32bit address-size is available, we can save one instruction:
lea dx, [eax + 0x3030] ; need a 32bit addressing mode to use eax as a source reg. Adds '0' to both digits at once, with a different destination.
mov al, dh ; then get ready to print the high byte first
The lea
needs an address-size prefix and a 2-byte mod/rm, and a 32bit displacement, so it loses badly on code-size, but it does save one instruction.
Using lea
to read from eax
after div
writes ax
will probably be faster on Sandybridge-family CPUs, esp. Haswell and later, but on Intel pre-SnB, the partial register stall will make it better to use the pure 16bit version with separate add and mov instructions.
Of course if you actually cared about performance, you'd use a multiplicative inverse instead of actually dividing by 10. And you usually wouldn't be writing 16-bit code that makes legacy BIOS calls either!
You need a print routine to print bytes as numbers, instead of writing them directly to the screen as characters. Luckily since you only have to deal with values between 0 and 59, and since you want leading zeroes, the problem is pretty simple. Assuming value to be printed in AX:
print2Digits:
;; input in AX (0-99)
;; clobbers AX and DX, save them if needed
MOV DL, 0Ah ; divide by: 10
DIV DL ; first digit in AL (quotient), second digit in AH (remainder)
MOV DX, AX ; save the digits
ADD AL, 30h ; ASCII '0'
MOV AH, 0Eh ; set up print
INT 10h ; print first digit.
MOV AL, DH ; retrieve second digit
ADD AL, 30h
INT 10h ; print it
RET
You are trying to display the values (time) in CH
, CL
and DH
registers as aschi characters.
Lets say your CH
contains the value 15h.
Now when you put this value in to AL
and call int 10h
, it displays the aschi character that matches the value 15h. If you want to show the decimal digits for 15h, then you will have to call a display routine which would break the value in the register and extracts the digits in it to display - not just the aschi representation in that value.
Lets say you have decimal 85 in a register. Now you need to print on screen "8" and "5". So you have to convert value 85 in to to aschi-56("8") and aschii-53("5"). Then put them in registers one after another and call int 10
twice to display "85".
hobbs answer shows how to do that.
Here is another tutorial