![]() | ![]() |
![]() |
![]() |
![]() |
|||||||||||||||||||||||||||||||||||||
![]() ![]() ![]() ![]()
|
![]() | ![]() | ![]() | ![]() |
|
||||||||||||||||||||||||||||||||||||
![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
Зачем может понадобиться низкоуровневое программирование клавиатуры? Самое, пожалуй, главное, это то, что при написании очень многих программ (в основном, конечно, игр) необходимо уметь "улавливать" одновременное нажатие нескольких клавиш (например, одновременное нажатие стрелки вверх и пробела и т.д). Стандартные средства BIOS позволяют это делать, но только не с любыми клавишами, а с функциональными (такими, как Shift, Alt и др). В самом деле, у неискушенного программиста может сложиться впечатление, что, например, Shift - клавиша особенная, так как она, якобы, изменяет значение остальных, в то время как на самом деле она с точки зрения контроллера клавиатуры абсолютно ничем не отличается от всех остальных клавиш. Различия осуществляются только на уровне BIOS.
Другая причина прямого программирования контроллера клавиатуры - это нежелание программиста разрешать BIOS обработку нажатых клавиш, например, с целью блокирования работы комбинаций Ctrl+Break или Ctrl+Alt+Del. Отказ от использования буфера ввода - тоже вынужденная необходимость, так как некоторые версии BIOS при нажатии на клавишу выдают очень короткий звуковой сигнал, который, конечно, будет порить собственные звуки программы (например фоновую музыку).
2. Принцип работы контроллера клавиатуры и его интерфейс с BIOS
Контроллер клавиатуры работает следующим образом: при нажатии или отпускании любой клавиши генерируется байт (так называемый скан-код), первые 7 битов которого содержат порядковый номер клавиши, а последний, седьмой бит, сброшен, если клавиша была нажата, и установлен, если отпущена. Этот скан-код можно прочитать через порт 60h (на самом деле внутри контроллера клавиатуры есть некая очередь скан-кодов, а порт 60h лишь отображает верхний код, но об этом можно и не знать). И еще. Как только клавиша нажимается или отпускается, вызывается 9-е прерывание (IRQ 1).
Но не всегда нажатие или отпускание клавиши генерирует один скан-код. Например, нажатие клавиши Pause вызывает генерацию сразу 5-и кодов. Нажатие белой стрелки вверх вызывает скан-код 72, а черной стрелки вверх - сразу 2 кода: 224 и 72. И для каждого из этих скан-кодов вызывается 9-е прерывание.
Таким образом, процедура обработки клавиатуры BIOS, "сидящая" на 9-м прерывании, просто анализирует значение 60h-го порта и соответствующим образом модифицирует буфер ввода. Все процедуры BIOS далее работают не с текущем значением порта, а с буфером ввода, что позволяет осуществлять ввод с опережением, то есть даже тогда, когда система занята.
3. Программирование клавиатуры
Если встала необходимость прямой работы с клавиатурой, применяется следующий метод: на 9-е прерывание устанавливается "заплата", которая первым делом обрабатывает состояние порта, а затем, при желании, передает управление старому обработчику клавиатуры BIOS. Если управление BIOS не передается, необходимо не забыть команду вывода значениа 20h а порт 20h, чтобы разрешить следующие прерывания от клавиатуры.
Теперь рассмотрим, в чем же заключается обработка порта клавиатуры. Необходимо обеспечить такой механизм, при котором программа смогла бы узнать, какие клавиши в данный момент нажаты, то есть иметь информацию о состоянии каждой клавиши. Предположим, это будет массив из 256-и элементов, по одному на каждую клавишу, где нулевое значение говорит о том, что соответствующая клавиша отпущена, и ненулевое, что нажата. Если полученный процедурой обработки 9-го прерывания скан-код имеет установленный 7-й бит, это значит, что клавиша отпущена, то есть нужно обнулить соответствующий элемент массива, а если сброшен - то присвоить соответствующий элемент в 1. Серые и белые клавиши (например, стрелки) считаются различными. Например, код серой стрелки вверх будет (128+72), а белой - 72.
Рассмотрим примеры работы с клавиатурой на различных языках программирования. В главной программе для "открытия" клавиатуры нужно вызывать OpenKeyboard с параметром, который говорит, нужно ли блокировать обработчик BIOS.
Си
void interrupt (*SvInt09)(void)=NULL; int IsBIOSActive=1; char KeyPressed[256]; char CurKey; void ProcessKeyb(void) { static PrevKey=0; char key,IsGray; key=inportb(0x60); if(PrevKey==224) IsGray=0x80; else IsGray=0; if(key!=224) /* если не признак черной клавиши"... */ { if(key&0x80) /* клавиша отпущена */ KeyPressed[(key&0x7F)|IsGray]=0; else /* клавиша нажата */ KeyPressed[(key&0x7F)|IsGray]=1; } if(!(key&0x80)) CurKey=key|IsGray; PrevKey=key; } void interrupt NewInt09(void) { ProcessKeyb(); if(IsBIOSActive) SvInt09(); /* не блокировать BIOS? */ else outportb(0x20,0x20); /* ... нужно блокировать */ } void CloseKeyboard(void); /* предварительное определение */ void OpenKeyboard(int LockBIOS) { memset(KeyPressed,0,256); CurKey=0; SvInt09=getvect(9); setvect(9,NewInt09); IsBIOSActive=!LockBIOS; atexit(CloseKeyboard); } void CloseKeyboard(void) { if(!SvInt09) return; /* клавиатура не открыта */ setvect(9,SvInt09); SvInt09=NULL; }
Паскаль:
var SvInt09 : procedure; SvExitProc : pointer; IsBIOSActive: boolean; KeyPressed : array[0..255] of boolean; CurKey : byte; procedure ProcessKeyb; const PrevKey: byte=0; var key,IsGray: byte; begin key:=Port[$60]; if PrevKey=224 then IsGray:=$80 else IsGray:=0; if key<>224 then { если не признак черной клавиши" } begin if key>127 then { клавиша отпущена } KeyPressed[key-128+IsGray]:=false else { клавиша нажата } KeyPressed[key+IsGray]:=true; end; if key<128 then CurKey:=key; PrevKey:=key; end; procedure NewInt09; interrupt; begin ProcessKeyb; if IsBIOSActive then { не блокировать BIOS } begin Inline($9C); SvInt09 end; else Port[$20]:=$20; { если нужно блокировать } end; procedure CloseKeyboard; forward; procedure OpenKeyboard(LockBIOS: boolean) begin FillChar(KeyPressed,256,0); CurKey:=0; GetIntVec(9,@SvInt09); SetIntVec(9,@NewInt09); IsBIOSActive:=not LockBIOS; SvExitProc:=ExitProc; ExitProc:=@CloseKeyboard; end; procedure CloseKeyboard; begin if @SvInt09=nil then Exit; { клавиатура не открыта } SetIntVec(9,@SvInt09); @SvInt09:=nil; ExitProc:=SvExitProc; end;Теперь в любом (!) месте программы можно анализировать массив KeyPressed, не задумываясь о том, каким образом он обновляется. Например:
Си
if(KeyPressed[72]&&KeyPressed[77]) {...} /* если одновременно нажаты вверх и вправо, то ... */
Паскаль:
if KeyPressed[72] and KeyPressed[77] then ... { если одновременно нажаты вверх и вправо, то ... }Безусловно, существуют и другие возможности по программированию контроллера клавиатуры (например, включение/выключение ее лампочек). Однако эти возможности используются уж очень редко.
В заключение можно привести одну полезную информацию об обработчике клавиатуры BIOS. Байт памяти с адресом 40h:17h содержит информацию о состоянии специальных клавиш клавиатуры:
Бит 7 - INSert активен Бит 6 - CapsLock активен Бит 5 - NumLock активен Бит 4 - ScrollLock активен Бит 3 - Alt нажат Бит 2 - Ctrl нажат Бит 1 - LeftShift нажат Бит 0 - RightShift нажат
Символическое обозначение этого байта можно представить так:
Паскаль:
var BIOSKeybState: byte absolute $40:$17;
Си:
#define BIOSKeybState (*(char far *)0x00400017L)
3 ноября 2000, 17:21
Дмитрий Котеров
dkLab, ©1999-2018