Как проверить статус ключей в сборке x86?
В прошлом январе я воспринял сборку x86 как хобби, поэтому я мог создавать игры, которые будут работать на старых компьютерах с 8086-м процессором, таких как PCj и Tandy 1000, но книги, которые я нашел, точно не учат многим по этой конкретной теме. В то время как некоторые DOS и BIOS прерывает работу, они далеки от совершенства.
Моя основная проблема - чтение состояния клавиатуры для нажатых клавиш без остановки программы. Я нашел несколько методов, но они очень ограничены. INT 21h, AH 0Ch читает последнюю нажатую клавишу, но в текстовом режиме. Мало того, что он читает только одну клавишу за раз, но обнаружение удара, похожее на блокнот, делает невозможным узнать, как долго удерживается клавиша. Я также видел ссылки на порты от 60h до 64h во время моих поездок в Google, но это просто ссылки. Актуальные объяснения и рабочий код практически отсутствуют. Или, может быть, я просто плохо использую поисковые системы.
Что мне нужно знать, так это то, нажата ли клавиша или нет. Лучшим решением было бы иметь буфер / массив всех клавиш клавиатуры и читать его состояние; 1 означает, что он не работает, 0 означает, что нет. Или просто иметь доступ к списку последних ключей, которые были нажаты и отпущены, было бы неплохо (конечно, с возможностью очистки этого буфера). Может кто-то указать мне верное направление?
Изменить: Во-первых, я должен был упомянуть, что я использую Borland TASM. Теперь я скомпилировал ваш код, и он прекрасно работает и все такое, хотя я почти стесняюсь признать, что не понимаю и половины этого. Я пытался сделать его совместимым с TASM, но все, что он делает, это создает мусор на экране и зависает.
Вот что я придумал;
.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
Я не уверен, правильно ли я закодировал прерывание. И черт, если я знаю, как работают порты 060h - 064h.
3 ответа
Вот как вы можете это сделать:
; 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, "$"
Запустите его в DOS/Win9x/NT/2K/XP/32-битной Vista/7 или DosBox.
ОБНОВЛЕНИЕ: версия TASM:
; 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
Пример опроса клавиатуры с использованием порта 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
Как правило, для старых систем, подобных этой, люди использовали BIOS в некоторой степени, как предварительно поставленный набор библиотечных функций, где такие вещи, как функции клавиатуры, используются, только если они удобны. В вашем случае службы клавиатуры BIOS не удобны, поэтому вы не используете их.
Вместо этого вы хотите заменить обработчик прерываний клавиатуры BIOS собственным обработчиком прерываний клавиатуры и внедрить собственный драйвер клавиатуры. Клавиатура использует IRQ1, то есть прерывание 9. Таблица векторов прерываний начинается с 0x0000:0x0000, поэтому вы хотите получить 4 байта по 0x0000:9*4 = 0x0000:0x0024 и сохранить их где-нибудь (чтобы вы могли вернуть вещи обратно в нормальное состояние при выходе из вашего программного обеспечения) и вместо этого поместите адрес (смещение, затем сегмент) вашего собственного обработчика IRQ клавиатуры.
Чтобы написать свой собственный драйвер клавиатуры, вы должны начать с понимания того, что задействовано 2 устройства. В компьютере есть микросхема контроллера клавиатуры (или "контроллер PS/2"), которая взаимодействует (посредством последовательной связи) с микросхемой внутри самой клавиатуры.
Для получения информации о чипе контроллера клавиатуры, смотрите что-то вроде http://wiki.osdev.org/%228042%22_PS/2_Controller
Для получения информации о чипе внутри самой клавиатуры, смотрите что-то вроде http://wiki.osdev.org/PS/2_Keyboard