8086 Assembly (TASM): Displaying an ASCII characte

2019-03-14 10:23发布

问题:

** Edited for clarification and "cleaner" code.

I'm trying to accept a character from the keyboard (any character) and convert it's ASCII value to hex, then display it.

I know how to convert from base 10 to hex, but just to make sure I'm not using incorrect terminology:

If I enter "c" in as my ASCII value, it's decimal value is 63. 63 divided by 16 (hex is base 16) = 3.9375. Save the quotient of 3 for later. Remainder * base (.9375 * 16) = 15. 15 is Hex character "F".

Quotient divided by base (3 / 16) = 0.1875. Quotient is zero, so this is the last step in conversion. Remainder * base (.1875 * 16) = 3

Read them from last to first ("first in, last out", if thinking about the stack), and I get "3F" as the Hex value for the decimal number "63" (which is ASCII for "c")

This is correct, yes?

It's pretty simple to read, I hope. I just get the character from the keyboard (AL), set BX as the divisor (base 16), and then divide AL by BX, store the remainder in the stack, and later on loop through the stack and try to display it...

I think my problem is that something is wrong with my multiplication and with my INT 21H/02H. I'm not sure if I need to add 30h in order to display the character or not...

On top of that, I'm not even sure if I need to be at that point (displaying) yet anyway, because I still haven't figured out how to convert 10-15 to A-F for hex.

I tried speaking with my teacher, and after waiting 30 minutes for him to finish speaking to another gaggle of students in my class (about something from another one of his classes, and not this one...which irked me quite a bit), the most that I got was "start over".

When asked about "what about using SHR and SHL?" as it was pointed out to me, but I was told that it can be done without that, and that I can't use those commands (they haven't been covered in the class).

Any input on what I'm doing wrong? Thanks!

    CHAR2HEX PROC                   ; Accept a character, print it's ascii value in hex.

        MOV DX, OFFSET AskChar      ; Display prompt
        MOV AH, 09H
        INT 21H

        MOV AX, 0                   ; Clear AX

        MOV AH, 07H                 ; Get keyboard input w/ no echo (AL)
        INT 21H

        MOV BX, 16                  ; Set up the divisor (base 16)
        MOV CX, 0                   ; Initialize the counter
        MOV DX, 0                   ; Clear DX

        Divide:                         
                                    ; Dividend (what's being divided) in DX/AX pair, Quotient in AX, Remainder in DX.
            DIV BX                  ; Divide (will be word sized).
            PUSH AX                 ; Save DX (the remainder) to stack.

            ADD CX, 1               ; Add one to counter

            MOV DX, 0               ; Clear Remainder (DX)
            CMP AX, 0               ; Compare Quotient (AX) to zero
            JNE Divide              ; If AX not 0, go to "Divide:"

        Multiply:
                                    ; Multiply remainder (from stack) by 16 to get hex value.               
            MOV DX, 0               ; Clear DX           
            POP AX                  ; Get remainder from stack into AX.

            MUL BX                  ; Multiply AX * BX.  (DX= high order bits, AX = low order bits)
            MOV DX, AX

            SUB DL, 30h             ; ADD 30h to DL 
            MOV AH, 02h             ; 02h to display AH (DL)
            INT 21H                 ; Send to DOS

            LOOP Multiply           ; If more to do, Multiply again
                                    ; LOOP subtracts 1 from CX. If non-zero, loop.
            RET
    CHAR2HEX ENDP
END START

EDITED **

I finally got it! I was able to get the program to return the hex value of the ascii char that was pushed on the keyboard, but it only worked if each remainder was 0 through 9. It wouldn't display the A through F, and instead used colons, semicolons, etc...

I looked at an Ascii/Deicmal/Hex chart online, and noticed that the characters 0 through 9 are 30h through 39h. But character A (for hex 10) doesn't start until 40h. So I changed the program so that if the value was greater than 39h, it added 7h to DL and THEN display it.

    CHAR2HEX PROC                   ; Accept a character, print it's ascii value in hex.

        MOV DX, OFFSET AskChar      ; Display prompt
        MOV AH, 09H
        INT 21H

        MOV AH, 07H                 ; Get keyboard input w/ no echo (AL)
        INT 21H

        MOV CL, AL                  ; Copy user input (AL) to CL
        MOV AX, 0                   ; Clear AX (get rid of HO bits)
        MOV AL, CL                  ; Copy user input back into AL

        MOV BX, 16                  ; Set up the divisor (base 16)
        MOV CX, 0                   ; Initialize the counter
        MOV DX, 0                   ; Clear DX

        Div2:                         
                                    ; Dividend (what's being divided) in DX/AX pair, Quotient in AX, Remainder in DX.
            DIV BX                  ; Divide (will be word sized).
            PUSH DX                 ; Save DX (the remainder) to stack.

            ADD CX, 1               ; Add one to counter
            MOV DX, 0               ; Clear Remainder (DX)
            CMP AX, 0               ; Compare Quotient (AX) to zero
            JNE Div2              ; If AX not 0, go to "Div2:"

        getHex2:
            MOV DX, 0               ; Clear DX.
            POP DX                  ; Put top of stack into DX.
            ADD DL, 30h             ; Conv to character.

            CMP DL, 39h
            JG MoreHex2

        HexRet2:        

            MOV AH, 02h             ; 02h to display AH (DL)
            INT 21H                 ; Send to DOS

            LOOP getHex2            ; If more to do, getHex2 again
                                    ; LOOP subtracts 1 from CX. If non-zero, loop.
            JMP Skip2
        MoreHex2:
            ADD DL, 7h
            JMP HexRet2             ; Return to where it left off before adding 7h.
        Skip2:
            RET
    CHAR2HEX ENDP

回答1:

Your logic for base 10 conversion looks good: each remainder from dividing by 10 will give you a digit, and they will be ordered from the least significant digit to the most significant one, so you need to reverse the obtained digits before printing them. Pushing them to a stack and popping them when done will do the reversing in a nice and compact way.

For converting to hex, the operation is the same: divide by 16, get the remainder, reverse, and print. However, there are a few things to note: dividing by 16 is shifting right 4 bits. Remainder is value AND 0fH, so you don't actually need any arithmetic operations. Bit shifts and AND operation is sufficient. Moreover, you don't even need to involve the stack. You can do this from most significant nibble (four bits) to the least significant one, and print as you calculate them. The second thing that you seem to be struggling with is converting a nibble to a hex digit. Simply adding 30h is not enough. It is good enough for decimals, but for hex values, you've got the numbers 10 to 15 as well. These need to be added to 41h ('A' in ASCII) minus 10 instead. Alternatively, you can put the digits '0' to '9' and 'A' to 'F' in a table, and index them by the value of the nibble you want to print.

You may also want to write a routine that will read a number from input. Read each digit until you read a newline, convert character to decimal value, and update an accumulator that will keep the number you've read. You will need to use this for both hex and decimal output functions.



回答2:

Write your program in C or some other language first. dont use the languages libraries, for example dont use printf("%x\n",number) to display hex, but take the number do the repeated divide by 10s as you suggest, saving them somewhere. Now remember the remainders (modulo) from dividing by ten is something between 0-9 to display this somewhere in ascii you need to add 0x30 as you mentioned. 123 becomes 12 remainder 3, then 12 becomes 1 remainder 2, and 1 becomes 0 remainder 1, 1+0x30 = 0x31 display that 2+0x30 becomes 0x32 display that, 3+0x30 becomes 0x33 display that.

hex is no different, the base is 16 not 10. As vhallac mentions though you can mask and shift instead of using the divide operator.

Once you have the "program" working in C or some other language, at a low level using basic operations (add, sub, divide, shift, and, or, etc) then re-write that code in assembly language.

Eventually you may get to the point where you dont need to prove out your program in some other language and start with asm.