Справочник программиста на персональном компьютере фирмы IBM. Клавиатура - Перепрограммирование прерывания клавиатуры
ОГЛАВЛЕНИЕ
3.1.9 Перепрограммирование прерывания клавиатуры.
Когда микропроцессор клавиатуры помещает скан-код в порт A
микросхемы 8255 (адрес порта 60H - см. [1.1.1]), то при этом
вызывается прерывание 9. Задача этого прерывания - преобразовать
скан-код символа, основываясь на состоянии клавиш-переключателей,
и поместить его в буфер клавиатуры. (Если скан-код соответствует
клавише-переключателю, то в буфер клавиатуры не пишется ничего,
за исключением случая клавиши <Ins>, а вместо этого прерывание
изменяет байты статуса, расположенные в области данных BIOS
[3.1.7]). Прерывания "ввода с клавиатуры" DOS и BIOS на самом
деле всего лишь прерывания "ввода из буфера клавиатуры". На самом
деле они не распознают нажатия клавиш. Точнее, они читают интерп-
ретацию введенных клавиш, которую обеспечило прерывание 9. Заме-
тим, что PCjr использует специальную процедуру (INT 48H) для
преобразования ввода от его 62 клавиш к 83-клавишному протоколу,
используемому другими IBM PC. Результат этой процедуры передается
прерыванию 9, которое выполняет свою работу как обычно. Прерыва-
нием 49H PCjr обеспечивает специальные неклавишные скан-коды,
которые потенциально могут устанавливаться периферийными уст-
ройствами, использующими инфракрасную (беспроволочную) связь с
клавиатурой.
Требуется весьма необычное применение, чтобы имело смысл пе-
репрограммировать это прерывание, особенно учитывая, что MS DOS
позволяет Вам перепрограммировать любую клавишу клавиатуры
[3.2.6]. Если все же Вам придется перепрограммировать прерывание
9, то эта глава даст Вам основы для старта. Сначала надо прочи-
тать [1.2.3], чтобы понимать как программируются прерывания. В
прерывании клавиатуры можно выделить три основных шага:
1. Прочитать скан-код и послать клавиатуре подтвердающий сиг-
нал.
2. Преобразовать скан-код в номер кода или в установку оегист-
ра статуса клавиш-переключателей.
3. Поместить код клавиши в буфер клавиатуры.
В момент вызова прерывания скан-код будет находиться в порте
A. Поэтому сначала надо этот код прочитать и сохранить на стеке.
Затем используется порт B (адрес 61H), чтобы быстро послать сиг-
нал подтверждения микропроцессору клавиатуры. Надо просто устано-
вить бит 7 в 1, а затем сразу изменить его назад в 0. Заметим,
что бит 6 порта B управляет сигналом часов клавиатуры. Он всегда
должен быть установлен в 1, иначе клавиатура будет выключена. Эти
адреса портов применимы и к AT, хотя он и не имеет микросхемы
интерфейса с периферией 8255.
Сначала скан-код анализируется на предмет того, была ли клави-
ша нажата (код нажатия) или отпущена (код освобождения). На всех
машинах, кроме AT, код освобождения индицируется установкой бита
7 скан-кода в 1. Для AT, у которого бит 7 всегда равен 0, код
освобождения состоит из двух байтов: сначала 0F0H, а затем
скан-код. Все коды освобождения отбрасываются, кроме случая кла-
виш-переключателей, для которых делаются соответствующие измене-
ния в байтах их статуса. С другой стороны, все коды нажатия обра-
батываются. При этом опять могут изменяться байты статуса кла-
виш-переключателей. В случае же символьных кодов, надо проверять
байты статуса, чтобы определить, например, что скан-код 30 соот-
ветствует нижнему или верхнему регистру буквы A.
После того как введенный символ идентифицирован, процедура
ввода с клавиатуры должна найти соответствующий ему код ASCII или
расширенный код. Приведенный пример слишком короток, чтобы рас-
смотреть все случаи. В общем случае скан-коды сопоставляются
элементам таблицы данных, которая анализируется инструкцией XLAT.
XLAT принимает в AL число от 0 до 255, а возвращает в AL 1-байт-
ное значение из 256-байтной таблицы, на которую указывает DS:BX.
Таблица может находиться в сегменте данных. Если в AL находился
скан-код 30, то туда будет помещен из таблицы байт номер 30 (31-й
байт, так как отсчет начинается с нуля). Этот байт в таблице
должен быть установлен равным 97, давая код ASCII для "a". Конеч-
но для получения заглавной A нужна другая таблица, к которой
обращение будет происходить, если статус сдвига установлен. Или
заглавные буквы могут храниться в другой части той же таблицы, но
в этом случае к скан-коду надо будет добавлять смещение, опреде-
ляемое статусом клавиш-переключателей.
Наконец, номера кодов должны быть помещены в буфер клавиатуры.
Процедура должна сначала проверить, имеется ли в буфере место для
следующего символа. В [3.1.1] показано, что этот буфер устроен
как циклическая очередь. Ячейка памяти 0040:001A содержит указа-
тель на голову буфера, а 0040:001C - указатель на хвост. Эти
словные указатели дают смещение в области данных BIOS (которая
начинается в сегменте 40H) и находятся в диапазоне от 30 до 60.
Новые символы вставляются в ячейки буфера с более старшими адре-
сами, а когда достигнута верхняя граница, то следующий символ
переносится в нижний конец буфера. Когда буфер полон, то указа-
тель хвоста на 2 меньше указателя на голову - кроме случая, когда
указатель на голову равен 30 (начало области буфера), а в этом
случае буфер полон, когда указатель хвоста равен 60.
Для вставки символа в буфер, надо поместить его в позицию, на
которую указывает хвост буфера и затем увеличить указатель хвоста
на 2; если указатель хвоста был равен 60, то надо изменить его
значение на 30. Вот и все. Схема прерывания клавиатуры показана
на рис. 3-4.
Низкий уровень.
Эффективная процедура требует глубокого продумывания. В этом
примере даны только самые зачатки. Он принимает только буквы на
нижнем и верхнем регистрах, причем все они загружены в одну таб-
лицу, в которой буквы верхнего регистра находятся на 100 байт
выше, чем их младшие братья. Анализируется только левая клавиша
сдвига и текущее состояние клавиши CapsLock игнорируется.
;---в сегменте данных
TABLE DB 16 DUP(0) ;пропускаем 1-е 16 байт
DB 'qwertyuiop',0,0,0,0 ;верхний ряд клавиатуры
DB 'asdfghjkl',0,0,0,0,0 ;средний ряд клавиатуры
DB 'zxcvbnm' ;нижний ряд клавиатуры
DB 16 DUP(0) ;пропуск до верхнего регистра
DB 'QWERTYUIOP',0,0,0,0 ;те же символы на верхнем
DB 'ASDFGHJKL',0,0,0,0,0 ;регистре
DB 'ZXCVBNM' ;
;---в начале программы устанавливаем прерывание
CLI ;запрет прерываний
PUSH DS ;сохраняем регистр
MOV AX,SEG NEW_KEYBOARD ;DS:DX должны указывать на
MOV DS,AX ;процедуру обработки
MOV DX,OFFSET NEW_KEYBOARD ;прерывания
MOV AL,9 ;номер вектора прерывания
MOV AH,25H ;номер функции DOS
INT 21H ;меняем вектор прерывания
POP DS ;восстанавливаем регистр
STI ;разрешаем прерывания
Программа продолжается, затем оставаясь резидентной [1.3.4].
;---это само прерывание клавиатуры
NEW_KEYBOARD PROC FAR ;сохраняем все изменяемые
PUSH AX ;регистры
PUSH BX ;
PUSH CX ;
PUSH DI ;
PUSH ES ;
;---получаем скан-код и посылаем сигнал подтверждения
IN AL,60H ;получаем скан-код из порта A
MOV AH,AL ;помещаем копию в AH
PUSH AX ;сохраняем скан-код
IN AL,61H ;читаем состояние порта B
OR AL,10000000B ;устанавливаем бит 7
OUT 61H,AL ;посылаем измененный байт в порт
AND AL,01111111B ;сбрасываем бит 7
OUT 61H,AL ;возвращаем состояние порта B
;---ES должен указывать на область данных BIOS
MOV AX,40H ;устанавливаем сегмент
MOV ES,AX ;
POP AX ;возвращаем скан-код из стека
;---проверка клавиши сдвига
CMP AL,42 ;нажат левый сдвиг?
JNE KEY_UP ;нет - смотрим следующее
MOV BL,1 ;да - изменяем бит статуса
OR ES:[17H],BL ;меняем прямо регистр статуса
JMP QUIT ;выход из процедуры
KEY_UP: CMP AL,170 ;левый сдвиг отпущен?
JNE NEXTKEY ;нет - смотрим следующее
MOV BL,11111110B ;да - меняем бит статуса
AND ES:[17H],BL ;меняем прямо регистр статуса
JMP QUIT ;выход из процедуры
NEXTKEY: ;просмотр других переключателей
;---это символьная клавиша - интерпретируем скан-код
TEST AL,10000000B ;код освобождения клавиши?
JNZ QUIT ;да - выходим из процедуры
MOV BL,ES:[17H] ;иначе берем байт статуса
TEST BL,00000011B ;клавиша сдвига нажата?
JZ CONVERT_CODE ;нет - уходим дальше
ADD AL,100 ;да - значит заглавная буква
CONVERT_CODE: MOV BX,OFFSET TABLE ;готовим таблицу
XLAT TABLE ;преобразуем скан-код в ASCII
CMP AL,0 ;возвращен 0?
JE QUIT ;если да, то на выход
;---код клавиши готов, проверяем не полон ли буфер клавиатуры
MOV BX,1AH ;смещение указателя на голову
MOV CX,ES:[BX] ;получаем его значение
MOV DI,ES:[BX]+2 ;получаем указатель хвоста
CMP CX,60 ;голова на вершине буфера?
JE HIGH_END ;да - переходим к спец. случаю
INC CX ;увеличиваем указатель головы
INC CX ;на 2
CMP CX,DI ;сравниваем с указателем хвоста
JE QUIT ;если равны, то буфер полон
JMP GO_AHEAD ;иначе вставляем символ
HIGH_END: CMP DI,30 ;проверка спец. случая
JE QUIT ;если буфер полон, то выход
;---буфер не полон - вставляем в него символ
GO_AHEAD: MOV ES:[DI],AL ;помещаем символ в позицию хвоста
CMP DI,60 ;хвост в конце буфера?
JNE NO_WRAP ;если нет, то добавляем 2
MOV DI,28 ;иначе указатель хвоста = 28+2
NO_WRAP: ADD DI,2 ;получаем новое значение хвоста
MOV ES:[BX]+2,DI ;посылаем его в область данных
;---завершение прерывания
QUIT: POP ES ;восстанавливаем изменяемые
POP DI ;регистры
POP CX ;
POP BX ;
POP AX ;
MOV AL,20H ;выдаем сигнал об окончании
OUT 20H,AL ;аппаратного прерывания
IRET ;возврат из прерывания
NEW_KEYBOARD ENDP