在DOS下同时检测/接收多个按键?

时间:2016-05-04 06:06:02

标签: assembly x86 game-engine tasm dosbox

我在编写空气曲棍球的过程中遇到了一个问题,就是我一次得到两个键/点击的原因,因为我需要同时点击两次才能同时移动两个玩家我尝试了很多,但我不认为我有办法做到这一点。 我听说我需要从缓冲区目录中读取并查看哪些键并单独阅读每个键但我不知道该怎么做。

1 个答案:

答案 0 :(得分:5)

你介意使用扫描码吗?

我知道这不是您正在寻找的简单,简单的解决方案,但我担心没有。 所以我写这篇文章是希望,如果不是你,其他一些有问题的编码人员可以找到有用的东西。

我也知道你为TASM写的但是我忘了并且从NASM开始,转换然而应该非常简单(只需添加段声明,从括号中取出段寄存器并添加 PTR

从硬件角度看,键盘只重复最后按下的键 1 ,所以如果两个玩家按下两个键,实际上只有一个按键发送键盘。
但是,软件会在任何地方同时处理多个密钥,他们是如何做到的?

诀窍是按下按键时键盘发送两个代码扫描码):当按键向下时,键盘发出一个许多其他的,当它被保持下来)和一个被释放 因此,软件可以在没有键盘 2 的其他通知的情况下判断键何时关闭。

我找不到任何处理按键上下事件的interrupt service

唯一的解决方案是直接处理扫描码。

虽然8042 chip非常简单,但IO指令不需要弄脏 在没有解释8259A和IRQ映射如何工作的情况下,足以说明当按下/释放某个键时,15h/AH=4fh中的扫描码会调用中断AL < / p>

我们可以拦截该中断并检查扫描码是用于按键还是按键 发布扫描码设置 bit7 我们可以有一个128字节的数组,每个数组用于任何可能的扫描码值( bit0-6 3 ,如果扫描码表示a,则存储在每个元素0ffh中如果它指示发布,请按或00h

保持已处理扫描码的计数也很有用,因此程序可以通过简单的算术等待新的扫描码。

现在,我想你仍然感到困惑 我写了一个演示。

以下程序,对于NASM,等待您按 d 键退出。
警告由于我们使用的是扫描码,因此这取决于键盘布局!

BITS 16
ORG 100h        ;COM

 ;Setup ISR for the scancode 
 call init 

 ;Clear screen
 mov ax, 03h
 int 10h 

 ;Print command
 mov ah, 09h 
 mov dx, strCommand
 int 21h 

_main:
 ;Wait for a change in the scancode tables
 call wait_for_scancode
 ;Remove unused keystrokes
 call remove_keystrokes

 ;Check if a is pressed 
 mov al, 1eh           ;a
 call is_scancode_pressed
 jz _main 

 ;Check if 'd' is pressed 
 mov al, 20h           ;d 
 call is_scancode_pressed
 jz _main 

 ;Both are pressed, print bye and ...
 mov ah, 09h 
 mov dx, strDone
 int 21h 

 ;... restore the ISR and ...
 call dispose 

 ;... exit
 mov ax, 4c00h
 int 21h

 strCommand db "Press 'a' and 'd' to exit", 13, 10, 24h 
 strDone    db "Bye",13,10,13,10,24h 

 ;Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  L  
 ;  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll
 ;Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  L

 ;S C A N C O D E   F U N C T I O N S

 ;Set the ISR 
init:
  push ax  

  mov ax, cs
  mov WORD [old_isr_15 + 02h], ax          
  ;old_isr_15 is now a far pointer to new_isr_15

  call swap_isr_15          ;Swap the current isr15 with the one in old_isr_15

  pop ax 
  ret

  ;Restore the original ISR 
dispose:
  call swap_isr_15          ;Swap the current isr15 with the one in old_isr_15                  

  ret  

  ;Swap the pointer in the IVT for int 15h with the pointer in old_isr_15
swap_isr_15:
  push eax
  push es 

  xor ax, ax 
  mov es, ax 

  cli

  mov eax, DWORD [es: 15h*4]
  xchg eax, DWORD [old_isr_15]
  mov DWORD [es: 15h*4], eax 

  sti

  pop es 
  pop eax

  ret  

  ;Wait for a change in the scancode table
wait_for_scancode:
 cli                           ;Prevent the ISR from messing things up 

 ;At least one scancode processed?
 cmp WORD [new_scancode], 0 
 jne _wfs_found                ;Yes

 ;No, restore interrupt so the CPU can process the prending ones
 sti
jmp wait_for_scancode

 ;New scancode, decrement the count and restore interrupts
_wfs_found:
 dec WORD [new_scancode]
 sti 

 ret

  ;THe BIOS is still saving keystrokes, we need to remove them or they 
  ;will fill the buffer up (should not be a big deal in theory).
remove_keystrokes:
 push ax

 ;Check if there are keystrokes to read.
 ;Release scancodes don't generate keystrokes 
_rk_try:
 mov ah, 01h 
 int 16h
 jz _rk_end      ;No keystrokes present, done 

 ;Some keystroke present, read it (won't block)
 xor ah, ah 
 int 16h
jmp _rk_try

_rk_end:
 pop ax 
 ret 

 ;Tell if a scancode is pressed
 ;
 ;al = scancode  
 ;ZF clear is pressed 
is_scancode_pressed:
  push bx

  movzx bx, al 
  cmp BYTE [scancode_status + bx], 0 

  pop bx 
  ret 

 ;AL = scancode 
new_isr_15:
 ;Check for right function
 cmp ah, 4fh
 jne _ni15_legacy

 ;Save used regs
 push bx
 push ax


 movzx bx, al            ;BX = scancode 
 and bl, 7fh             ;BX = scancode value

 sar al, 07h             ;AL = 0ffh if scancode has bit7 set (release), 00h otherwise
 not al                  ;AL = 00h if scancode has bit7 set (release), 0ffh otherwise

 ;Save the scancode status
 mov BYTE [cs:bx + scancode_status], al 
 ;Increment the count
 inc WORD [cs:new_scancode]

 pop ax
 pop bx 

_ni15_legacy:   
 ;This is a far jump, in NASM is simply jmp FAR [cs:old_isr_15]
 ;Ended up this way for debug
 push WORD [cs: old_isr_15 + 02h] 
 push WORD [cs: old_isr_15] 
 retf

 ;Original ISR
old_isr_15                      dw new_isr_15, 0  

 ;Scan code status table
scancode_status     TIMES 128   db 0
 ;Scan code count 
new_scancode                    dw 0

您需要使用的是:

  • init设置扫描码侦听。
  • dispose拆除扫描码侦听。
  • is_scancode_pressed知道某个键是否被按下。

其他所有内容,包括wait_for_scancoderemove_keystrokes都是附件,只是为了让您的程序表现得非常好。

如果要查找与某个键相关联的扫描码,可以使用this other program显示一个包含所按扫描码的表格(按 ESC 退出)。
例如,如果我按 a d ,我会获得演示中使用的值

Me pressing 'a' and 'd'

这只是上述演示的变体。

1 我创建this启动程序(用于NASM)来测试我的假设,至少在我的硬件中。

2 实际上,重复功能仅在键入软件时有用,其他每个应用程序(如游戏)明确检查键状态都不需要它。

3 实际上有扩展(多字节扫描码)但这里忽略它们。坚持使用普通键!