Справочник программиста на персональном компьютере фирмы IBM. Клавиатура
Страница 10. Перепрограммирование прерывания клавиатуры


     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

 
« Предыдущая статья   Следующая статья »