21.6. Скэн-коды клавиатуры и работа с ними
Чтение скэн-кодов клавиш — это самый низкий уровень программного опроса клавиатуры. Каждая нажатая клавиша выдает свой
- 506 -
уникальный скэн-код, который зависит только от ее местоположения на клавиатуре и никак не зависит от того, что под ней понимается. Так, арифметические знаки на алфавитной и цифровой клавиатурах имеют различные скэн-коды. Скэн-коды полностью совместимой с IBM PC ПЭВМ приведены в табл. 21. 2.
Таблица 21. 2
Клавиша
Скэн-код
(16)
Скэн-код
(10)
Клавиша
Скэн-код
(16)
Скэн-код
(10)
Esc
$01
1
Z
$2C
44
!1
$02
2
X
$2D
45
@2
$03
3
C
$2E
46
#3
$04
4
V
$2F
47
$4
$05
5
B
$30
48
%5
$06
6
N
$31
49
^6
$07
7
M
$32
50
&7
$08
8
<,
$33
51
*8
$09
9
>.
$34
52
(9
$0A
10
?/
$35
53
)0
$0B
11
Shift(правый)
$36
54
_-
$0C
12
PrintScreen
$37
55
+=
$0D
13
Alt
$38
56
BackSpace
$0E
14
Пробел
$39
57
TAB
$0F
15
CapsLock
$3A
58
Q
$10
16
F1
$3B
59
W
$11
17
F2
$3C
60
E
$12
18
F3
$3D
61
R
$13
19
F4
$3E
62
T
$14
20
F5
$3F
63
Y
$15
21
F6
$40
64
U
$16
22
F7
$41
65
I
$17
23
F8
$42
66
O
$18
24
F9
$43
67
P
$19
25
F10
$44
68
{[
$1A
26
NumLock
$45
69
}]
$1B
27
ScrollLock
$46
70
Enter
$1C
28
7 Home
$47
71
Ctrl
$1D
29
8 Вверх
$48
72
- 507 -
A
$1E
30
9 PgUp
$49
73
S
$1F
31
Серый -
$4A
74
D
$20
32
4 Влево
$4B
75
F
$21
33
$4C
76
G
$22
34
6 Вправо
$4D
77
H
$23
35
Серый +
$4E
78
J
$24
36
1 End
$4F
79
K
$25
37
2 Вниз
$50
80
L
$26
38
3 PgDn
$51
81
:;
$27
39
0 Ins
$52
82
“ '
$28
40
. Del
$53
83
~`
$29
41
F11
$D9
217
Shift (левый)
$2A
42
F12
$DA
218
|
$2B
43
Проверить правильность этой таблицы на любой другой ПЭВМ (с MS-DOS и Турбо Паскалем, конечно) можно при помощи программы опроса скэн-кода нажатой клавиши (рис. 21. 10).
| USES CRT, DOS;
| VAR
| Ch, ExtCh : Char; {символы с клавиатуры }
| Scan, LastScan : Byte; {скэн-коды клавиш }
| OldInt09H : Pointer; {адрес старого вектора }
| {$F+}
| PROCEDURE IntProc; INTERRUPT; {перехват прерывания }
| BEGIN
| Scan:=Port[$60]; {чтение скэн-кода }
| Inline($FF/$1E/>OldInt09H); {возврат прерывания }
| END;
| {$F-}
| BEGIN
| GetIntVec($09, OldInt09H); {взятие адреса прерывания }
| SetIntVec($09, @IntProc); {подстановка перехватчика }
Рис. 21.10
- 508 -
| Scan := 128;
| { стартовое значение Scan }
| WriteLn('Нажимайте что угодно.', 'Esc - выход из программы.');
| repeat { Основной цикл опроса: }
| Ch := #0;
| ExtCh := #0;
| { сброс значений до опроса }
| repeat
| until Scan<128; { ожидание любого нажатия }
| Write( ' Скэн-код=', Scan:3 );
| if KeyPressed { Клавиша - не регистровая? }
| then Ch:=ReadKey; { да, ее код запоминается }
| if KeyPressed and (Ch=#0) {Клавиша - функциональная? }
| then ExtCh := ReadKey; { да, запоминается расш. код }
| { вывод итогов опроса: }
| Write ( 'Символ', Ch + ExtCh );
| GotoXY( 30, WhereY ); { нейтрализация кода 13 }
| WriteLn(('” Код=', Ord( Ch ):3, ' Расш. код=', Ord( ExtCh ) );
| LastScan := Scan; { нужен последний скэн-код }
| Scan := 128; { снова стартовое значение }
| until LastScan=1; { условие конца — нажата Esc }
| SetIntVec($09, OldInt09H);
| { восстановление прерывания }
| ReadLn { пауза до нажатия ввода }
| END.
Рис. 21.10 (окончание)
Программа перехватывает низкоуровневое прерывание номер 9 и запоминает содержимое порта, через который передаются коды нажатых клавиш, в глобальной переменной Scan. После этого анализируется, внесло ли нажатие что-либо в буфер ввода. Если да, то выводится информация о нажатой клавише. Используя перехват прерывания, кaк это сделано в примере, можно проводить и более сложный анализ (рис. 21.11). После каждого нажатия любой клавиши перехватчик записывает в Scan скэн-код. Но здесь есть особенность: при нажатии клавиши вырабатывается истинный скэн-код, а при отпускании — увеличенный на 128. Поэтому в примере ожидание нажатия возложено на цикл
repeat until Scan < 128;
который размыкается только при нажатии клавиши (Scan содержит число, меньшее 128) и не реагирует на отпускание их.
На рис. 21.11 рассматривается каркас Паскаль-программы, позволяющей «отлавливать» одновременное нажатие нескольких регистровых клавиш вместе с алфавитной клавишей или без нее.
Аналогичным методом можно определять факты нажатия практически всех распознаваемых ПЭВМ комбинаций клавиш. Надо
- 509 -
| { КАРКАС ПРОГРАММЫ, РЕАГИРУЮЩЕЙ НА СПЕЦИАЛЬНЫЕ КОМБИНАЦИИ}
| { НАЖАТИЙ НА КЛАВИАТУРЕ }
| USES CRT, DOS;
| {Необходим модуль DOS. CRT нужен для примера. }
| VAR { глобальные переменные программы : }
| OldInt09H : Pointer; { адрес прерывания 09 }
| CtrlRShiftD : Boolean; { флаг нажатия комбинации }
| CONST { Константы специальной комбинации клавиш: }
| HotKey = $20; { скэн-код клавиши [D]; }
| KlavReg = 1+4; { значение в байте $0:$0417 при нажатии }
| { левого регистра Shift вместе с Ctrl : }
| { 1 - нажата правая клавиша Shift (бит 0); }
| { 4 - Нажато Ctrl+Shift (бит номер 2). }
| {$F+}
| PROCEDURE IntProc; INTERRUPT; {перехват прерывания 09Н }
| VAR
| M: Byte absolute $000:$417; { байт состояния регистров }
| C,L,H : Byte; { значение скэн-кода и др. }
| BEGIN
| С := Port[$60]; { чтение скэн-кода }
{Устанавливаем флаг нажатия, анализируя скэн-код и состояние байта нажатия клавиш регистров: }
| CtrlRShiftD:=(C=HotKey) and ((M and KlavReg)=KlavReg);
| if CtrlRShiftD
| then begin { Специальная обработка }
| L:=Port[$61]; H:=L; { портов, если нажата }
| L:=L or $80; { требуемая комбинация }
| Port[$61] :=L;
| Port[$61]:=H;
| Port[$20] :=$20
| end
| else { Иначе пусть выполняется }
| inline($FF/$1E/>OldInt09H); { настоящее прерывание }
| END;
| {$F-}
| VAR
| 1 : Word; {=== ОСНОВНАЯ ЧАСТЬ ПРИМЕРА ==== }
| LABEL Stop;
| BEGIN
| CtrlRShiftD:=False; {обязательное стартовое значение! }
| ClrScr:
| Write('Нажатие Ctrl+Пр.Shift+D приостановит цикл. ');
| GetIntVec($09, OldInt09H); { сохраняем старый вектор }
| SetIntVec($09, @IntProc ); { подставляем новый вектор }
| {...}
Рис. 21.11
- 510 -
| for i:=1 to 30000 do
| begin { рабочий цикл }
| GotoXY( 5,5 );
| Write( 1:6, ' из 30000.');
| { Программа должна периодически проверять, было ли }
| { одновременное нажатие Ctrl+Правая Shift + D ? }
| if CtrlRShiftD
| then { Да. Обработка нажатия горячего ключа }
| begin
| Write('Комбинация нажата...Закончить(Y/N)? ');
| if UpCase( ReadKey )='Y'
| then
| Goto Stop;
| ClrScr;
| { ... }
| end
| else
| { Нет. Дальнейшие действия программы }
| begin
| {...}
| end; {if}
| end; {for i}
| { конец рабочего цикла }
| { ... }
| Stop: { метка выхода из цикла }
| SetIntVec{ $09, OldInt09H );
| { вернем исходный вектор }
| ClrScr
| END.
Рис. 21.11 (окончание)
только устанавливать соответствующие значения скэн-кодов «горячих» клавиш в HotKey и числа для побитового сравнения с байтами $417/$418 в KlavReg.
Недостатком примера является задержка между нажатием комбинации клавиш и началом анализа. Избежать ее можно, если вставить вызовы процедур реакции на «горячие» комбинации прямо в Interrupt-процедуру. Однако не советуем спешить это сделать. При такой организации программы необходимо принимать специальные меры по предохранению содержимого регистров процессора, блокированию прочих прерываний, их согласованию и т.п., о чем можно всегда прочитать в любой толстой книге по системному программированию на языке ассемблера. В большинстве же программ может пригодиться и предложенный способ.