如何使用DOS x86汇编在屏幕上显示一个数字并休眠一秒钟?

5

我正在使用NASM 16位汇编语言。我想编写一个简单的汇编程序,可以在每个数字之间1秒钟的时间间隔内打印0到255的所有数字。以下是我的部分代码:

[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

我不确定如何在屏幕上打印ax的值,并且如何等待1秒钟(将它们放在代码的注释中)。


@PavanManjunath 是的,我正在PC上进行操作。Windows 7,32位。 - Jean Carlos Suárez Marranzini
关于延迟部分,您可以查看帖子的最后一篇。时钟被假定为100 MHz。但是您需要通过试错来确定您机器上的确切值。这个链接也应该能帮助您正确地延迟。 - Pavan Manjunath
@PavanManjunath,我正在检查这些链接,它们非常有趣,非常感谢。我会尽力理解,因为我还不太擅长汇编程序设计。如果有其他帮助或者对这个问题的答案,我将不胜感激。 - Jean Carlos Suárez Marranzini
1个回答

6

在段0偏移46Ch(或者在seg 40h,offs 6Ch)有一个4字节的计数器,由PC BIOS维护和更新。它每秒增加18.2次。计算此计数器的最低字节或字中的18个变化可能是等待大约一秒钟最简单的方法:

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

要打印十进制数,您需要将二进制数转换为十进制数,获取单个数字并打印它们。您将数字除以10并收集余数。例如:

123:
123 / 10:商12,余数3
12 / 10:商1,余数2
1 / 10:商0,余数1

通过重复除以10,您可以获得余数中的单个数字以相反的顺序:3,2,1。然后,使用DOS int 21h函数2打印它们(将2加载到AH中,将字符的ASCII代码加载到DL中,执行int 21h)。

另一种替代方案,非常适合您的问题,是使用DAA指令直接在十进制中增加数字而无需进行任何转换。

以下是如何完成所有操作的方法:

; 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,"$"

如果您不喜欢前导零或最后1秒的延迟,可以有条件地跳过它们。
下载描述每个指令工作方式的Intel和/或AMD x86 CPU手册。阅读它们。此外,下载“Ralf Brown的中断列表”,其中描述了每个BIOS和DOS函数。您需要了解其中一些内容才能进行I/O操作。还有“HelpPC”和“TechHelp”,方便地描述了许多BIOS和DOS事物,如上述计数器所在的“BIOS数据区”。

这条评论是给@Alex的。它完美地运行了,非常感谢你慷慨的帮助。我会确保阅读你在上面评论中说的一切。只有一个问题,修改你发布的代码以使过程向后进行(从255到0)难吗? - Jean Carlos Suárez Marranzini
在基于div和基于daa的情况下,这都是微不足道/容易的。 - Alexey Frunze
1
如果在DOS中可以工作,PITRTC也是其他选项。 - Ciro Santilli OurBigBook.com
还要记得,如果之前清除了 cli,那么你需要执行 sti - Ciro Santilli OurBigBook.com

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接