How to check the status of keys in x86 assembly?

I took the x86 build as a hobby last January so I can create games that will work on older 8086-powered computers like PCj and Tandy 1000, but the books I found do not explain this specific topic very well. Although some dos and bios interrupt their work, they are far from perfect.

My main problem is reading the keyboard state for the pressed keys without stopping the program. I found several methods, but they are very limited. INT 21h, AH 0Ch reads the last key pressed, but in a text editor. It not only reads only one key at a time, but notepad detection makes it impossible to know how long the key is held. I also saw links to ports from 60 to 64 hours during my travels on Google, but these are just links. Actual explanations and working code practically do not exist. Or maybe I'm just using search engines poorly.

I need to know if the key is held or not. A better solution would be to have a buffer / array of all the keyboard keys and read its state; 1 means it is omitted, 0 means no. Or just accessing the list of the latest keys that have been deleted and issued would be nice (with the option to clear this buffer, of course). Can someone point me in the right direction?

Edit: First, I should have mentioned that I use Borland TASM. Now I compiled your code, and it works fine and that’s all, although I’m almost embarrassed to admit that I don’t understand half of it. I tried to make it compatible with TASM, but all it does is create trash on the screen and freeze it.

Here is 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 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 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 I even encoded the interrupt right. And hell, if I know how ports 060h - 064h work.

+7
source share
3 answers

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 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 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 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 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 
+3
source

As a rule, for older systems like this, people used the BIOS a bit as a set of ready-made sets of library functions, where things like keyboard functions are used only if they are convenient. In your case, BIOS keyboard services are not convenient, so you are not using 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 table of interrupt vectors starts at 0x0000: 0x0000, so you want to get 4 bytes at 0x0000: 9 * 4 = 0x0000: 0x0024 and store them somewhere (so that you can return things in normal mode, when your software exits) and instead puts the address (offset, then segment) of your own keyboard IRQ handler.

To write your own keyboard driver, you should begin to understand that there are 2 pieces of equipment involved. There is a keyboard controller chip (or β€œPS / 2 controller”) in a computer that speaks (via serial communication) with a chip inside the keyboard itself.

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

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

0
source

An example of polling a keyboard using port 60h and port 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 
0
source

All Articles