How to check keys status in x86 assembly?

I took x86 assembly as a hobby this past january so I could make games that would work on old 8086-powered computers like the PCj and Tandy 1000, but the books I found don't exactly teach much on that specific topic. While some dos and bios interrupts kind of do the job, they're far from perfect.

My main issue is reading the keyboard status for pressed keys without halting the program. I found a few methods, but they're very limited. INT 21h, AH 0Ch reads the last pressed key, but in a text-edition fashion. Not only does it read only one key at a time, but the notepad-like hit detection makes it impossible to know how long the key has been held. I've also seen references to the ports 60h to 64h during my Google travels, but it's just that, references. Actual explanations and working code is virtually non-existent. Or maybe I'm just that bad at using search engines.

What I need to know is whether a key is held down or not. The best solution would be to have a buffer/array of all the keyboard keys and read its state; 1 means it's down, 0 means it's not. Or just having access to a list of the last keys to have been hit and released would be nice (with a way to clear that buffer, of course). Can anyone point me in the right direction?

Edit: First off, I should have mentioned that I use Borland TASM. Now I compiled your code and it works great and all, even though I'm almost shy to admit I don't understand half of it. I tried to make it compatible with TASM but all it does is create garbage on the screen and freeze.

Here's what I came up with;

.MODEL TINY
.STACK 256

.DATA
kbdbuf DB 128 DUP (0)

msg1 db "Press and hold ESC", 13, 10, "$"
msg2 db "ESC pressed, release ESC", 13, 10, "$"
msg3 db "ESC released", 13, 10, "$"

.CODE
main PROC
    org 0100h
    mov ax, @data
    mov ds, ax

    xor     ax, ax
    mov     es, ax

    cli                         ; update ISR address w/ ints disabled
    push    word [es:9*4+2]     ; preserve ISR address
    push    word [es:9*4]
    lea si, irq1isr
    mov     word [es:9*4], si   ; requires a register
    mov     [es:9*4+2],cs
    sti

        mov     ah, 9
        lea     dx, msg1
        int     021h                ; print "Press and hold ESC"

    test1:
        mov     al, [kbdbuf + 1]    ; check Escape key state (Esc scan code = 1)
        or      al, al
        jz      test1               ; wait until it's nonzero (pressed/held)

        lea     dx, msg2
        int     021h                ; print "ESC pressed, release ESC"

    test2:
        mov     al, [kbdbuf + 1]    ; check Escape key state (Esc scan code = 1)
        or      al, al
        jnz     test2               ; wait until it's zero (released/not pressed)

        lea     dx, msg3            ; print "ESC released"
        int     021h

    cli                         ; update ISR address w/ ints disabled
    pop     word [es:9*4]       ; restore ISR address
    pop     word [es:9*4+2]
    sti

    ret

    irq1isr:
    push    ax bx

    ; read keyboard scan code
    in      al, 060h

    ; update keyboard state
    xor     bh, bh
    mov     bl, al
    and     bl, 07Fh            ; bx = scan code
    shr     al, 7               ; al = 0 if pressed, 1 if released
    xor     al, 1               ; al = 1 if pressed, 0 if released
    mov     [cs:bx+kbdbuf], al

    ; send EOI to XT keyboard
    in      al, 061h
    mov     ah, al
    or      al, 080h
    out     061h, al
    mov     al, ah
    out     061h, al

    ; send EOI to master PIC
    mov     al, 020h
    out     020h, al

    pop     bx ax
    iret
main ENDP

END main

I'm not sure if I even coded the interrupt right. And heck if I know how the ports 060h - 064h work.


Here's how you can do it:

; compile with NASM: nasm.exe -f bin kbd.asm -o kbd.com

bits 16
org 0x100

    xor     ax, ax
    mov     es, ax

    cli                         ; update ISR address w/ ints disabled
    push    word [es:9*4+2]     ; preserve ISR address
    push    word [es:9*4]
    mov     word [es:9*4], irq1isr
    mov     [es:9*4+2],cs
    sti

    call    test

    cli                         ; update ISR address w/ ints disabled
    pop     word [es:9*4]       ; restore ISR address
    pop     word [es:9*4+2]
    sti

    ret

test:
    mov     ah, 9
    mov     dx, msg1
    int     0x21                ; print "Press and hold ESC"

test1:
    mov     al, [kbdbuf + 1]    ; check Escape key state (Esc scan code = 1)
    or      al, al
    jz      test1               ; wait until it's nonzero (pressed/held)

    mov     dx, msg2
    int     0x21                ; print "ESC pressed, release ESC"

test2:
    mov     al, [kbdbuf + 1]    ; check Escape key state (Esc scan code = 1)
    or      al, al
    jnz     test2               ; wait until it's zero (released/not pressed)

    mov     dx, msg3            ; print "ESC released"
    int     0x21

    ret

irq1isr:
    pusha

    ; read keyboard scan code
    in      al, 0x60

    ; update keyboard state
    xor     bh, bh
    mov     bl, al
    and     bl, 0x7F            ; bx = scan code
    shr     al, 7               ; al = 0 if pressed, 1 if released
    xor     al, 1               ; al = 1 if pressed, 0 if released
    mov     [cs:bx+kbdbuf], al

    ; send EOI to XT keyboard
    in      al, 0x61
    mov     ah, al
    or      al, 0x80
    out     0x61, al
    mov     al, ah
    out     0x61, al

    ; send EOI to master PIC
    mov     al, 0x20
    out     0x20, al

    popa
    iret

kbdbuf:
    times   128 db 0

msg1 db "Press and hold ESC", 13, 10, "$"
msg2 db "ESC pressed, release ESC", 13, 10, "$"
msg3 db "ESC released", 13, 10, "$"

Run it in DOS/Win9x/NT/2K/XP/32-bit Vista/7 or DosBox.

UPDATE: TASM version:

; file: kbdt.asm
; compile with TASM/TLINK:
;   tasm.exe kbdt.asm
;   tlink.exe /t kbdt.obj

.286

code segment use16
assume cs:code, ds:code, ss:code
org 100h

main:
    xor     ax, ax
    mov     es, ax

    cli                         ; update ISR address w/ ints disabled
    push    word ptr es:[9*4+2]     ; preserve ISR address
    push    word ptr es:[9*4]
    mov     word ptr es:[9*4], offset irq1isr
    mov     es:[9*4+2],cs
    sti

    call    test0

    cli                         ; update ISR address w/ ints disabled
    pop     word ptr es:[9*4]   ; restore ISR address
    pop     word ptr es:[9*4+2]
    sti

    ret

test0:
    mov     ah, 9
    mov     dx, offset msg1
    int     21h                 ; print "Press and hold ESC"

test1:
    mov     al, [kbdbuf + 1]    ; check Escape key state (Esc scan code = 1)
    or      al, al
    jz      test1               ; wait until it's nonzero (pressed/held)

    mov     dx, offset msg2
    int     21h                 ; print "ESC pressed, release ESC"

test2:
    mov     al, [kbdbuf + 1]    ; check Escape key state (Esc scan code = 1)
    or      al, al
    jnz     test2               ; wait until it's zero (released/not pressed)

    mov     dx, offset msg3     ; print "ESC released"
    int     21h

    ret

irq1isr:
    pusha

    ; read keyboard scan code
    in      al, 60h

    ; update keyboard state
    xor     bh, bh
    mov     bl, al
    and     bl, 7Fh             ; bx = scan code
    shr     al, 7               ; al = 0 if pressed, 1 if released
    xor     al, 1               ; al = 1 if pressed, 0 if released
    mov     cs:[bx+kbdbuf], al

    ; send EOI to XT keyboard
    in      al, 61h
    mov     ah, al
    or      al, 80h
    out     61h, al
    mov     al, ah
    out     61h, al

    ; send EOI to master PIC
    mov     al, 20h
    out     20h, al

    popa
    iret

kbdbuf      db 128 dup (0)

msg1 db "Press and hold ESC", 13, 10, "$"
msg2 db "ESC pressed, release ESC", 13, 10, "$"
msg3 db "ESC released", 13, 10, "$"

code ends

end main

Typically for old systems like this people used the BIOS a bit like a pre-supplied set of library functions, where things like keyboard functions are only used if they've convenient. In your case the BIOS keyboard services aren't convenient, so you don't use them.

Instead, you want to replace the BIOS keyboard interrupt handler with your own keyboard interrupt handler and implement your own keyboard driver. The keyboard uses IRQ1, which is interrupt 9. The interrupt vector table starts at 0x0000:0x0000 so you'd want to get the 4 bytes at 0x0000:9*4 = 0x0000:0x0024 and store them somewhere (so you can put things back to normal when your software exits) and put the address (offset then segment) of your own keyboard IRQ handler there instead.

To write your own keyboard driver, you'd want to start by understanding that there's 2 pieces of hardware involved. There's the keyboard controller chip (or "PS/2 controller") in the computer which talks (via. serial communication) to a chip inside the keyboard itself.

For information on the keyboard controller chip, see something like http://wiki.osdev.org/%228042%22_PS/2_Controller

For information on the chip inside the keyboard itself, see something like http://wiki.osdev.org/PS/2_Keyboard


使用端口60h和端口64h轮询键盘的示例:

       cli            ; stop software-Interrupts
       mov  al, 2     ; stop IRQ 1
       out  21h, al
       sti
P1:
       in   al, 64h     ; get Status
       test al, 1       ;  is there something in the outputbuffer?
       jz  P1
       test al, 20h     ;  it is a byte from the PS2-Mouse?
       jnz  P1
       in   al, 60h     ; get a key
       cmp  al, 1       ;  Escape-key?
       jz  XRAUS        ; then goto end
;───────────────────────────────────────────────────────────────
       mov  si, OFFSET SONTAB  ; get the offsetaddress of our special-key table
       mov  cl, Extablen       ; lenght
XSUCH: cmp  al, [si]
       jz  XFOUND
       lea  si, [si+1]         ; instead of "inc si"
       dec  cl
       jnz XSUCH
;───────────────────────────────────────────────────────────────
       mov  si, OFFSET TASTTAB  ; get the offsetaddress of our key table
       mov  cx, tablen
       mov  bx, OFFSET TEXTTAB  ; our corresponding ASCII table
SUCH:  cmp  al, [si]
       jz  short FOUND
       lea  si, [si+1]
       dec  cx
       jnz SUCH
       jmp  P1
;───────────────────────────────────────────────────────────────
XRAUS: in   al, 60h ; clear outputbuffer
       cli
       xor  al, al  ; enable IRQ 1
       out  21h, al
       sti
       mov  ah, 1  ; clear buffer in the ram
       int  16h
; ...some more instructions
;───────────────────────────────────────────────────────────────
FOUND:  mov  si, tablen   ; Length
        sub  si, cx
        xor  ecx, ecx
        mov  cl, [bx+si]  ; get the ASCII from our table
;  ...some more instructions
;───────────────────────────────────────────────────────────────
XFOUND:
;  Tab,shift li.,shift re.,HOME,UP,LEFT,RIGHT,END,DOWN
        cmp  cl, 1       ; DOWN-key
        jnz short  ...   ; jump to next
        ....
        ....
        cmp  cl, 9       ; Tab-key
        jnz  P1
;  ...some more instructions
:------------------------Data area----------------------
TASTTAB DB 02h,03h,04h,05h,06h,07h,08h,09h,0Ah,0Bh,0Ch,0Dh
        DB 10h,11h,12h,13h,14h,15h,16h,17h,18h,19h,1Ah,1Bh,1Eh,1Fh
        DB 20h,21h,22h,23h,24h,25h,26h,27h,28h,29h,2Bh,2Ch,2Dh,2Eh,2Fh
        DB 30h,31h,32h,33h,34h,35h,39h
        DB 56h
tablen =  ($-TASTTAB)
TEXTTAB DB "1234567890ß'"     ; with some german letters inside
        DB "qwertzuiopü+as"
        DB "dfghjklöä^#yxcv"
        DB "bnm,.- "
        DB "<"
Textablen  =  ($-TEXTTAB)
;---------------------------------------------------------------------------
;  Tab,shift left.,shift rigth.,HOME,UP,LEFT,RIGHT,END,DOWN
;----------
SONTAB  DB 0Fh,2Ah,36h,47h,48h,4Bh,4Dh,4Fh,50h
Extablen  =  ($-SONTAB)
        DB 0,0,0 ; for data_alignment of following entries
链接地址: http://www.djcxy.com/p/10752.html

上一篇: 使用ETag启用HTTP 304缓存

下一篇: 如何检查x86汇编中的密钥状态?