How to check keys status in x86 assembly?

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Moderators General, Prelates

How to check keys status in x86 assembly?

Postby DieJay » Wed May 09, 2012 10:09 pm UTC

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 ASM books I managed to obtain 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?
Proud Duct-Tape Programmer since 2000.
"More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason - including blind stupidity."
-W.A. Wulf
User avatar
DieJay
 
Posts: 93
Joined: Tue Jun 30, 2009 4:45 pm UTC
Location: here be dragons

Re: How to check keys status in x86 assembly?

Postby jareds » Thu May 10, 2012 4:28 am UTC

First of all, you should be aware of Ralf Brown's Interrupt List.

You can use the INT 21h stdin functions without blocking -- you can use AH=0Bh to see whether a character is available.

However, the INT 21h stdin functions are designed for stream-of-char style input as in C stdin. The next step to a lower level would be the INT 16h keyboard functions. You would use either AH=11h/10h/12h or AH=01h/00h/02h (depending on whether your absurdly old hardware supports extended keystrokes or not (or if you need them in the first place)), to, respectively, see if a keystroke is available, get a keystroke (with scancode), and check the state of the modifier keys for the keystroke (shift/ctrl/alt). If you want to know how long a key is held down, just use AH=03h to set the repeat rate/delay as needed (although technically this doesn't distinguish between holding a key down and a user mashing at the exact repeat rate.

If you want to do anything more sophisticated, you can just replace the BIOS keyboard interrupt handler--this is DOS. Basically, what happens when a key is pressed or released is that the keyboard controller generates an interrupt, and the BIOS keyboard interrupt handler uses the ports you mentioned to retrieve and acknowledge the key press/release and then ultimately stores the keystroke into a buffer for you to retrieve via INT 16h (repeats are presumably handled by the timer interrupt). (Obviously, this assumes that are NOT using a USB keyboard.) Fortunately, I found a page that gives you an example of replacing the keyboard interrupt handler (see Using a Low Level Keyboard Driver and Writing the Low Level Keyboard Driver on that page. In fact, the author's example creating an array in exactly the way you want. (For the record, my search term was "replace dos keyboard interrupt".)

Technically, none of this is really specific to x86 assembly--it's specific to using the IBM PC compatible architecture without a modern OS.
jareds
 
Posts: 317
Joined: Wed Jan 03, 2007 3:56 pm UTC

Re: How to check keys status in x86 assembly?

Postby Yakk » Thu May 10, 2012 2:04 pm UTC

It is possible that you'll want to detect someone pressing a key, distinct from someone "currently has the key down". Or, at least, in my UI programming experience, unless you have both of these you get some really bad UI. (Ie, "press enter to continue", but the enter only counts when you poll the keyboard, and your code happened to be busy doing something else so didn't get around to polling it before the user lifted the enter key, resulting in the interface not responding to the enter keypress. The user responds by holding down enter -- which then ends up triggering a the "press enter" command on the page after the prompt.)

In general, you'll probably want a stream of incoming keyboard state changes. Each incoming keyboard state change carries with it what changed, and what the state of the keyboard is.

So when enter goes down, you the event consists of "enter is down, oh and by the way here is the rest of the keyboard state -- shift was down as well" and then "enter went up" later.

At any one point in your code's state, it will be in some keyboard state that isn't always what the state the physical keyboard is in at that moment. Coherency is important. Consume states off the keyboard message queue, instead of polling the current keyboard state, makes it much harder to "miss" a keypress from the user. Any kind of "discard queue" has to be carefully through through, as it is pretty easy to get into a state where enter or shift is down but you never saw the enter-down state start, and your program behaves wonkily.

In short: if you write a keyboard driver yourself, be careful and try to inform yourself based on the mistakes other keyboard handlers have run into!
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.
User avatar
Yakk
 
Posts: 10039
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: How to check keys status in x86 assembly?

Postby DieJay » Thu May 10, 2012 7:11 pm UTC

That's... even more confusing, actually. Or maybe I'm just really dumb.

Now I tried to whip up something from a post on StackOverflow.com, but all it does is create garbage on the screen. It's nice enough that it compiled at all. I use Borland TASM by the way.

EDIT: Ah, nevermind, I found the right way to do it! Here's the solution for future coders looking for help;

Code: Select all
.MODEL SMALL
.STACK 256
.8086

.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
    mov ax, @data
    mov ds, ax
   
    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

    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

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

    mov ah, 04Ch
    mov al, 0
    int 21h ; end program with code 0
main ENDP

irq1isr PROC
    push ax bx

    ; 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

    pop bx ax
    iret

irq1isr ENDP

end main


I still don't quite understand how the ports 060h - 064h work though. If you know of a web page explaining it in details, I'd be grateful to have it.
Proud Duct-Tape Programmer since 2000.
"More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason - including blind stupidity."
-W.A. Wulf
User avatar
DieJay
 
Posts: 93
Joined: Tue Jun 30, 2009 4:45 pm UTC
Location: here be dragons


Return to Coding

Who is online

Users browsing this forum: Tebychacy and 9 guests