Страница 10 из 26
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
|