How to convert from BCD to ASCII and print the result?

2.5k Views Asked by At

I want to output the RTC date.

I realized that I had to convert from BCD to ASCII in order to do that, but I do not know what to do.

To convert, I use this page: BCD to ASCII conversion, but it uses interrupt 21h and in this page: https://en.wikipedia.org/wiki/BIOS_interrupt_call interrupt 21h does not exist.

How can I convert and print BCD?

DAY:
mov ah,0x04
int 0x1a
mov al,dl
aam ;to convert.
mov bx,ax
mov cx,ax
add dl,ch
mov ah,02h
int 21h
mov dl,cl
int 21h

I tested the code in ubuntu. I think I should use interrupt 10h for output and not 21h. (int 21h is not active).

So, how to convert BCD to ASCII, and how to print it using interrupt 10h?
Just use following style?

mov al, 'h'
int 10h
1

There are 1 best solutions below

2
On BEST ANSWER

What is BCD (binary-coded decimal)?

In case of the BIOS time interrupt result, the "packed BCD" is used. Each byte (8 bits) is split into two halves of 4 bits (nibbles), each containing one decimal value (4 bits allow for 0-15 values, but only 0-9 values are used to stay "decimal").

So for example if the time is ending with "37" seconds, the seconds byte will contain value 0x37 = 55 = 0b00110111. Notice how both in hexadecimal and binary the 4-bit halves are visible/readable even by human after short practice (in decimal not, you have to calculate in head the division/remainder by 16 to split it into 3 and 7: 55 / 16 = 3, 55 % 16 = 7).

Now assembly is pretty straightforward with bit fiddling, so even if you don't want to delve into the specialized BCD instructions like AAM, you can use ordinary bit masking + shifting to extract the two values.

Let's say you have value 0x37 in BL, and you want to extract it into BH (0x03) and BL (0x07), then for example this code will calculate that:

mov bh, bl   ; copy the packed value also into BH
and bl, 0x0F ; keep only lower 4 bits in BL (0x37 & 0x0F = 0x07)
shr bh, 4    ; shift right by 4 bits BH (unsigned(0x37) >> 4 = 0x03)
; and that's it, it's that simple to manipulate bits in assembly

How to display it?

That depends on your target platform, judging from question and comments you are aiming for x86 16b real mode on PC compatible computer with only BIOS available, so one of the easiest ways is to convert those decimal 0-9 values into ASCII values of digits (adding 48 works, or many assemblers will do conversion from chars for you, so for example add bl,'0' works, and you can think about it as "add font character 0 to 0-9 value", and as the next digits in font are defined in +1 way from '0', it will calculate the correct character value).

And then use one of the int 10h services to display the ASCII character, like the AH=0x0A service. Example of displaying the ASCII character from BL:

mov ah, 0x0A  ; write ASCII character
mov al, bl    ; character value to write
xor bx, bx    ; bh = bl = 0 in text mode (adjust in gfx mode as needed)
mov cx, 1     ; write it only once
int 0x10      ; call the BIOS service

As you want to display the time, and you have the values in several registers, you will probably need to preserve them between printing, for example, if I would use int 0x1A,02 service to read RTC, I would use this "architecture" to display it (not writing full implementation, just the top logic to show you how you can use push/pop stack instructions to preserve values for later usage):

read_and_display_rtc_time:
    mov  ah,2
    int  0x1A     ; call service 00 of RTC BIOS interrupt
    push dx       ; preserve DH (seconds)
    push cx       ; preserve CL (minutes)
    mov  al,ch    ; AL = CH (hours)
    call display_bcd   ; display AL as BCD-packed decimal value
    call display_colon ; display ":"
    pop  ax       ; restore minutes (push cx) into AL
    call display_bcd   ; display AL as BCD-packed decimal value
    call display_colon ; display ":"
    pop  ax
    mov  al,ah    ; restore seconds into AL
    call display_bcd   ; display AL as BCD-packed decimal value
    ret

display_bcd:
    ; AL contains BCD-packed decimal value (4:4 bits)
    ; TODO code to split it into two values, convert to ASCII and display
    ret

display_colon:
    ; TODO code to display colon on screen
    ret

Now I will comment on some of your problems in comments:

mov cl,ax

Yes, that's not legal, because ax is 16 bit register, and cl is 8 bit register (alias for lower 8b part of cx), so you would lose 8 bits in such operation.

To show as programmer that you indeed do want to lose the upper 8 bits, you can write mov cl,al (al is alias of lower 8 bits of ax).

The opposite conversion like mov ax,cl will again fail, but again as a programmer you can define precisely, how the 8 bit value should be converted into 16 bits value.

For example to do "unsigned" conversion you can use either the 386+ instruction:

movzx ax,cl

Or 8086-80286 calculation way:

xor ax,ax   ; set all bits of AX to zero first
mov al,cl   ; copy the CL into lower 8 bits of AX

And for "signed" conversion there's again specialized 386+ way:

movsx ax,cl
; or before 80386 times there are two common options
mov   al,cl     ; if "ax" is target, then there's special instr.
cbw             ; sign extend AL into AX
; or when non-ax register is target, like DX
mov   dh,cl     ; copy CL into upper 8 bits first
sar   dx,8      ; now do sign-right-shift by 8 bits
  ; dx = sign extended CL

Just keep in mind the computer is a bit more complex calculator, nothing else, so you should try to convert from the word task into thinking which numerical values you have on the input side, what numeric values represent output side (even "characters" are just numeric values in computer), and then you just figure out mathematical formulas to convert the input numbers into output numbers.

If you have bochs, comment out code which does not compile yet, and start with small chunk of code which works, check in debugger what they are doing, so you can visually see how the calculation is going on, then add slowly new parts of code by 2-3 instructions, and keep running them in debugger to see if they calculate what you want. (the overuse of word "calculate" in recent paragraphs is intentional.. if you know how to operate the calculator, you are pretty close to know how to code in assembly, although your initial source will be probably ugly, as it takes time to pick up also "style" and efficiency - just check the instruction set to understand what kind of calculations are possible and get better insight into what means that AX is 16 bit register, and AL is 8 bit, and how the numeric values are encoded in bits (electricity current)).


"tested in ubuntu"

Not exactly, you ran just nasm in ubuntu to produce 16bit x86 machine code binary (cross-compiling, you can produce such binary on any other machine, which has x86 assembler, not really relevant to your questions, except you should specify NASM, so people trying to answer you will know which x86 assembly syntax to use, each assembler has tiny differences). Then you did try that binary in virtual machine (BOCHS). If you would try to run it directly under ubuntu, it would fail completely, as ubuntu OS doesn't support runtime environment for 16b real mode machine code.

When unsure what is relevant, just describe all of your tools, and command lines/options you use.