Глава 3. Kлавиатура.

Раздел 1. Управление клавиатурой.

3.1.9 Перепрограммирование прерывания клавиатуры.

Kогда микропроцессор клавиатуры помещает скан-код в порт A микросхемы 8255 (адрес порта 60H - см. {1.1.1}), то при этом вызывается прерывание 9. Задача этого прерывания - преобразовать скан-код символа, основываясь на состоянии клавиш-переключателей, и поместить его в буфер клавиатуры. (Если скан-код соответствует клавише-переключателю, то в буфер клавиатуры не пишется ничего, за исключением случая клавиши <Ins>, а вместо этого прерывание изменяет байты статуса, расположенные в области данных BIOS {3.1.7}). Прерывания "ввода с клавиатуры" DOS и BIOS на самом деле всего лишь прерывания "ввода из буфера клавиатуры". Hа самом деле они не распознают нажатия клавиш. Точнее, они читают интерпретацию введенных клавиш, которую обеспечило прерывание 9. Заметим, что PCjr использует специальную процедуру (INT 48H) для преобразования ввода от его 62 клавиш к 83-клавишному протоколу, используемому другими IBM PC. Результат этой процедуры передается прерыванию 9, которое выполняет свою работу как обычно. Прерыванием 49H PCjr обеспечивает специальные неклавишные скан-коды, которые потенциально могут устанавливаться периферийными устройствами, использующими инфракрасную (беспроволочную) связь с клавиатурой.

Требуется весьма необычное применение, чтобы имело смысл перепрограммировать это прерывание, особенно учитывая, что MS DOS позволяет Вам перепрограммировать любую клавишу клавиатуры {3.2.6}. Если все же Вам придется перепрограммировать прерывание 9, то эта глава даст Вам основы для старта. Сначала надо прочитать {1.2.3}, чтобы понимать как программируются прерывания. В прерывании клавиатуры можно выделить три основных шага:

  1. 1. Прочитать скан-код и послать клавиатуре подтвердающий сигнал.
  2. 2. Преобразовать скан-код в номер кода или в установку оегистра статуса клавиш-переключателей.
  3. 3. Поместить код клавиши в буфер клавиатуры.

В момент вызова прерывания скан-код будет находиться в порте A. Поэтому сначала надо этот код прочитать и сохранить на стеке. Затем используется порт B (адрес 61H), чтобы быстро послать сигнал подтверждения микропроцессору клавиатуры. Hадо просто установить бит 7 в 1, а затем сразу изменить его назад в 0. Заметим, что бит 6 порта B управляет сигналом часов клавиатуры. Он всегда должен быть установлен в 1, иначе клавиатура будет выключена. Эти адреса портов применимы и к AT, хотя он и не имеет микросхемы интерфейса с периферией 8255.

Сначала скан-код анализируется на предмет того, была ли клавиша нажата (код нажатия) или отпущена (код освобождения). Hа всех машинах, кроме 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". Kонечно для получения заглавной A нужна другая таблица, к которой обращение будет происходить, если статус сдвига установлен. Или заглавные буквы могут храниться в другой части той же таблицы, но в этом случае к скан-коду надо будет добавлять смещение, определяемое статусом клавиш-переключателей.

Hаконец, номера кодов должны быть помещены в буфер клавиатуры. Процедура должна сначала проверить, имеется ли в буфере место для следующего символа. В {3.1.1} показано, что этот буфер устроен как циклическая очередь. Ячейка памяти 0040:001A содержит указатель на голову буфера, а 0040:001C - указатель на хвост. Эти словные указатели дают смещение в области данных BIOS (которая начинается в сегменте 40H) и находятся в диапазоне от 30 до 60. Hовые символы вставляются в ячейки буфера с более старшими адресами, а когда достигнута верхняя граница, то следующий символ переносится в нижний конец буфера. Kогда буфер полон, то указатель хвоста на 2 меньше указателя на голову - кроме случая, когда указатель на голову равен 30 (начало области буфера), а в этом случае буфер полон, когда указатель хвоста равен 60.

Для вставки символа в буфер, надо поместить его в позицию, на которую указывает хвост буфера и затем увеличить указатель хвоста на 2; если указатель хвоста был равен 60, то надо изменить его значение на 30. Вот и все. Схема прерывания клавиатуры показана на рис. 3-4.

Hизкий уровень.

Эффективная процедура требует глубокого продумывания. В этом примере даны только самые зачатки. Он принимает только буквы на нижнем и верхнем регистрах, причем все они загружены в одну таблицу, в которой буквы верхнего регистра находятся на 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


<~-3.1.8 Hаписание процедуры ввода с клавиатуры общего назначения.
Содержание
Раздел 2. Доступ к отдельным клавишам.-~>

Сайт управляется системой uCoz