Search code examples
assemblyx86dosnasmbios

How to display a number on the screen and and sleep for one second with DOS x86 assembly?


I'm using NASM 16 BITS. I'm trying to do a simple assembly code that prints the numbers from 0 to 255 with 1 second interval between each number. This is what I have so far:

[bits 16]

mov ax,cs
mov ds,ax
mov cx,255
mov ax,0

myloop:
    ;print in screen ax value
    ;wait 1 second
    inc ax

loop myloop

I'm not sure how to print the value of ax in the screen, and how to wait 1 second(placed them in a comment in the code).


Solution

  • There's a 4-byte counter at segment 0 offset 46Ch (or alternatively at seg 40h, offs 6Ch) maintained and updated by the PC BIOS. It's incremented 18.2 times per second. Counting 18 changes in the lowest byte or word of this counter is probably the simplest way of waiting out approximately a second:

    mov  ax, 0
    mov  ds, ax
    mov  cx, 18
    mov  bx, [46Ch]
    WaitForAnotherChange:
    NoChange:
    mov  ax, [46Ch]
    cmp  ax, bx
    je   NoChange
    mov  bx, ax
    loop WaitForAnotherChange
    

    To print decimal numbers you need to convert binary numbers into decimal, get individual digits and print them. You divide the number by 10 and collect remainders. e.g.:

    123:
    123 / 10: quotient 12, remainder 3
    12 / 10: quotient 1, remainder 2
    1 / 10: quotient 0, remainder 1

    By repeatedly dividing by 10 you get the individual digits in the remainders in the reverse order: 3,2,1. Then you print them using DOS int 21h function 2 (load 2 into AH, load the character's ASCII code into DL, execute int 21h).

    An alternative variant, quite suited to your problem, would be to use the DAA instruction to increment the number directly in decimal without any conversion.

    Here's how it all can be done:

    ; file: counter.asm
    ; assemble: nasm.exe counter.asm -f bin -o counter.com
    
    bits 16
    org 0x100
    
        mov  ax, 0 ; initial number
        mov  cx, 256 ; how many numbers
    
    NextNumber:
    %if 1 ; change to 0 to use the DAA-based method
        push ax
    
        mov  dx, 0
        div  word [ten]
        push dx
    
        mov  dx, 0
        div  word [ten]
        push dx
    
        mov  dx, 0
        div  word [ten]
        push dx
    
        pop  dx
        call PrintDigit
        pop  dx
        call PrintDigit
        pop  dx
        call PrintDigit
    
        pop  ax
    
        call PrintNewLine
        call Wait1s
    
        inc  ax
    %else
        mov  dl, ah
        call PrintDigit
    
        mov  dl, al
        shr  dl, 4
        call PrintDigit
    
        mov  dl, al
        and  dl, 0Fh
        call PrintDigit
    
        call PrintNewLine
        call Wait1s
    
        add  al, 1
        daa
        adc  ah, 0
    %endif
    
        loop NextNumber
        ret
    
    PrintDigit:
        pusha
        mov   ah, 2
        add   dl, '0'
        int   21h
        popa
        ret
    
    PrintNewLine:
        pusha
        mov   dx, CRLF
        mov   ah, 9
        int   21h
        popa
        ret
    
    Wait1s:
        pusha
        push ds
    
        mov  ax, 0
        mov  ds, ax
    
        mov  cx, 18
        mov  bx, [46Ch]
    WaitForAnotherChange:
    NoChange:
        mov  ax, [46Ch]
        cmp  ax, bx
        je   NoChange
        mov  bx, ax
        loop WaitForAnotherChange
    
        pop  ds
        popa
        ret
    
    ten dw 10
    CRLF db 13,10,"$"
    

    If you don't like the leading zeroes or the last 1-second delay, you can conditionally skip them.

    Download Intel and/or AMD x86 CPU manuals that describe how each instruction works. Read them. Also, download the Ralf Brown's Interrupt List, which describes every BIOS and DOS function. You need to know some of them to do I/O. There are also HelpPC and TechHelp that conveniently describe many BIOS and DOS things like the BIOS Data Area where the aforementioned counter lives.