Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.15.150.59] |
|
Сообщ.
#1
,
|
|
|
Драйвер клавиатуры При написании своих ОС, или при переходе в защищенный режим становится проблематичным использовать прерывания BIOS для работы с клавиатурой. Но создать свой драйвер несложно, хотя разработчики клавиатур постарались, чтобы жизнь программистам не казалась совсем уж медом Разумеется будет рассмотрен только второй режим работы клавиатуры - для PC/AT. В этом режиме клавиатуру можно разбить на следующие группы, по генерируемым скан-кодам: Но не все так плохо. Если повнимательнее присмотреться к таблице скан-кодов, то нетрудно заметить что, отбросив комбинацию <E0h, 2Ah>, мы сможем обрабатывать клавиши дополнительной клавиатуры как расширенные, что значительно облегчает задачу, поскольку остается учитывать только поступление байт E0h и E1h. Ниже представлен код драйвера, для примера, написанный под DOS. Чем-то напоминает обработчик BIOS, но не обрабатывает комбинации клавиш. Поддерживает смену раскладки клавиатуры (Eng/Rus). .model tiny, pascal .8086 ;+---------------------------------------------------------------------------+ ;| объявление экспортируемых функций драйвера | ;+---------------------------------------------------------------------------+ public key_Install ; инициализация драйвера public key_Uninstall ; выгрузка и восстановление старого public key_Wait8042In ; ожидает готовности входного буфера i8042 public key_Wait8042Out ; ожидает готовности выходного буфера i8042 public key_Put ; добавляет очередной символ и его скэн-код в очередь public key_Get ; чтение символа из буфера клавиатуры public key_Status ; чтение флагов состояния регистровых клавиш ;+---------------------------------------------------------------------------+ ;| константы | ;+---------------------------------------------------------------------------+ ; биты состояния регистровых клавиш клавиатуры (Status[0]) LSHIFT equ 01h ; левый Shift RSHIFT equ 02h ; правый Shift LCONTROL equ 04h ; левый Ctrl RCONTROL equ 08h ; правый Ctrl LALT equ 10h ; левый Alt RALT equ 20h ; правый Alt INSERT equ 40h ; Insert SYSREQ equ 80h ; PrtSc/SysRq ; биты состояния драйвера и управления светодиодами (Status[1]) E0 equ 01h ; предыдущий скэн-код был E0h E1 equ 02h ; предыдущий скэн-код был E1h RUSLAT equ 04h ; Eng/Rus раскладка PAUSE equ 08h ; Pause/Break SCROLLLOCK equ 20h ; ScrollLock NUMLOCK equ 40h ; NumLock CAPSLOCK equ 80h ; CapsLock ;+---------------------------------------------------------------------------+ ;| данные | ;+---------------------------------------------------------------------------+ .data ; таблицы перекодировки, местами не менять! Symb: db 0 ,1bh,'1','2','3','4','5','6','7','8','9','0','-','=', 8 , 9 db 'q','w','e','r','t','y','u','i','o','p','[',']', 13, -1,'a','s' db 'd','f','g','h','j','k','l',';',"'",'`', -1,'\','z','x','c','v' db 'b','n','m',',','.','/', -1,'*', -1,' ', -1, 0 , 0 , 0 , 0 , 0 db 0 , 0 , 0 , 0 , 0 , -1, -1, 0 , 0 , 0 ,'-', 0 , 0 , 0 ,'+', 0 db 0 , 0 , -1, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 db 32 dup (0) SymbRus: db 0 ,1bh,'1','2','3','4','5','6','7','8','9','0','-','=', 8 , 9 db 'й','ц','у','к','е','н','г','ш','щ','з','х','ъ', 13, -1,'ф','ы' db 'в','а','п','р','о','л','д','ж','э',')', -1,'\','я','ч','с','м' db 'и','т','ь','б','ю','ё', -1,'*', -1,' ', -1, 0 , 0 , 0 , 0 , 0 db 0 , 0 , 0 , 0 , 0 , -1, -1, 0 , 0 , 0 ,'-', 0 , 0 , 0 ,'+', 0 db 0 , 0 , -1, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 db 32 dup (0) SymbShift: db 0 ,1bh,'!','@','#','$','%','^','&','*','(',')','_','+', 8 , 9 db 'Q','W','E','R','T','Y','U','I','O','P','{','}', 13, -1,'A','S' db 'D','F','G','H','J','K','L',':','"','~', -1,'|','Z','X','C','V' db 'B','N','M','<','>','?', -1,'*', -1,' ', -1, 0 , 0 , 0 , 0 , 0 db 0 , 0 , 0 , 0 , 0 , -1, -1,'7','8','9','-','4','5','6','+','1' db '2','3','0','.', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 db 32 dup (0) SymbRusShift: db 0 ,1bh,'!','"','/','$',':',',','.',';','?','%','_','+', 8 , 9 db 'Й','Ц','У','К','Е','Н','Г','Ш','Щ','З','Х','Ъ', 13, -1,'Ф','Ы' db 'В','А','П','Р','О','Л','Д','Ж','Э','(', -1,'|','Я','Ч','С','М' db 'И','Т','Ь','Б','Ю','Ё', -1,'*', -1,' ', -1, 0 , 0 , 0 , 0 , 0 db 0 , 0 , 0 , 0 , 0 , -1, -1,'7','8','9','-','4','5','6','+','1' db '2','3','0','.', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 db 32 dup (0) ; код переключения раскладки клавиатуры (Rus/Eng) LangSwitch db 1Dh ; клавиша - Ctrl db RCONTROL ; флаги - правый ;+---------------------------------------------------------------------------+ ;| неинициализированные данные | ;+---------------------------------------------------------------------------+ .data? ; состояние клавиатуры Status db ? ; состояние регистровых клавиш db ? ; состояние драйвера и светодиодов ; буфер драйвера клавиатуры buf_tail dw ? ; указатель на последний введенный символ buf_head dw ? ; указатель на первый введенный символ buf_code dw 32 dup(?) ; сам буфер buf_end equ ($-buf_code)/2 ; адрес старого обработчика прерываний клавиатуры oldOfsIRQ1 dw ? ; его смещение oldSegIRQ1 dw ? ; и сегмент ; старое состояние светодиодов oldLed db ? ;+---------------------------------------------------------------------------+ ;| код | ;+---------------------------------------------------------------------------+ .code ; обработчик IRQ 1 - прерывания, генерируемого контроллером клавиатуры key_Handler proc ; сохраняем используемые нами регистры и настраиваем регистр DS на сегмент данных push ds es ax bx cx dx si di bp push cs pop ds ; ds - наш сегмент данных (для модели TINY) ; получаем пришедший байт mov dx, 60h in al, dx mov ah, al ; для IBM XT и более ранних моделей необходимо уведомить контроллер клавиатуры ; о приеме данных, через порт 61h. если не предполагается поддержка таких ; древних компьютеров, то можно смело убрать следующие 6 строк кода. inc dx in al, dx or al, 80h ; устанавливаем режим "только чтение" out dx, al ; отсылаем контроллеру and al, 7fh ; отменяем режим "только чтение" out dx, al ; теперь контроллер готов к посылке следующего байта ; разрешаем прерывания, поскольку задерживать их крайне нежелательно mov al, 20h ; al - команда EOI (End Of Interrupt) out 20h, al ; отсылает ее контроллеру прерываний sti ; теперь вызываем функцию 4fh, прерывания 15h. ; эта функция доступна на всех ЭВМ, кроме PC, PCjr, XT ; от 11/08/82 и AT от 01/10/84. mov al, 4fh xchg al, ah ; al - скэн-код / ah - номер функции int 15h ; на выходе: ; флаг CF = 0 - игнорировать скан-код ; 1 - занести скан-код в буфер клавиатуры ; AL = скан-код (старый или подмененный) jnc @@iret ; начинаем обработку пришедшего символа xor bx, bx ; номер таблицы перекодировки mov ah, al test Status[1], E1 ; если установлен бит E1, jnz @@pause ; <- то идет прием кодов Pause/Break ; если у скэн-кода установлен старший бит, то пришел код отжатой клавиши ; или начало последовательности кодов расширенной/дополнительной клавиатуры or al, al jns @@isextcode cmp al, 0e0h ; E0h - первый байт скэн-кода расширенной/доп. клавиатуры jne @@ispause or Status[1], E0 ; устанавливаем признак приема расширенного скэн-кода jmp @@iret ; и будем дожидаться собственно байта скэн-кода клавиши @@ispause: cmp al, 0e1h ; E1h - начальный байт Pause/Break jne @@keyup or Status[1], E1 ; устанавливаем признак приема скэн-кода от Pause/Break jmp @@iret ; и ждем остальную последовательнось байт @@keyup: call key_Release ; из отжатых нас интересуют Ctrl, Alt и Shift jmp @@exit @@isextcode: test Status[1], E0 ; если установлен бит E0, то идет прием очередного jnz @@extcode ; байта скэн-кода клавиши расширенной/доп. клавиатуры ; получен однобайтовый код основной клавиатуры, ; пытаемся преобразовать его в ascii символ, с учетом состояния регистровых клавиш test Status[1], RUSLAT jz @@isshift inc bx @@isshift: test Status[0], (LSHIFT or RSHIFT) jz @@isnumlock inc bx inc bx @@isnumlock: ; если включен NumLock, то обрабатываем немного по другому test Status[1], NUMLOCK jz @@xlat cmp al, 47h ; на цифровой клавиатуре? jb @@xlat cmp al, 53h ja @@xlat neg bx ; меняем 0|1 таблицу на 2|3 таблицу add bx, 3 @@xlat: shl bx, 7 ; bx = bx * 128 = bx * sizeof(таблица перекодировки) add bx, offset Symb xlat ; al = bx[al] = SymbTable[al] cmp al, -1 ; это регистровая клавиша? je @@keydown ; <- да, нужно изменить ее статус test Status[1], CAPSLOCK jz @@savecode call key_ChangeReg ; при включенном SapsLock меняем регистр клавиш @@savecode: call key_Put ; ложим получившийся код в буфер @@exit: and Status[1], not E0 @@iret: pop bp di si dx cx bx ax es ds iret ; обработка нажатия Pause/Break (последовательность кодов - E1h,1Dh,45h,E1h,9Dh,C5h) @@pause: cmp al, 0c5h ; это конец последовательности? jne @@exit and Status[1], not E1 xor Status[1], PAUSE; меняем состояние на противоположное jmp @@exit ; пришел код расширенной/доп. клавиатуры @@extcode: cmp al, 2ah je @@exit ; <- пропускаем код E0h,2Ah ; выясняем, не регистровая ли клавиша нажата? @@keydown: mov al, ah ; команды с регистром AL короче на один байт :) mov dh, LALT cmp al, 38h ; нажат Alt? je @@isrightdn ja @@isled ; коды 3Ah и выше принадлежат клавишам управления светодиодами mov dh, LSHIFT cmp al, 2ah ; нажат левый Shift? je @@savedn mov dh, RSHIFT cmp al, 36h ; нажат правый Shift? je @@savedn mov dh, LCONTROL cmp al, 1dh ; нажат Ctrl? je @@isrightdn cmp al, 37h ; нажат Print Screen? jne @@unccode xor Status[0], SYSREQ jmp @@exit @@isrightdn: test Status[1], E0 ; если предыдущий код был E0h jz @@savedn ; то это правый Ctrl/Alt add dh, dh ; сдвигаем бит влево (делаем клавишу правой) @@savedn: or Status[0], dh ; устанавливаем бит нажатой регистровой клавиши jmp @@exit ; и уходим ; проверяем на нажатие клавиши управления светодиодами и Insert @@isled: mov dh, SCROLLLOCK cmp al, 46h ; нажат ScrollLock? je @@led mov dh, NUMLOCK cmp al, 45h ; нажат NumLock? je @@led mov dh, CAPSLOCK cmp al, 3ah ; нажат CapsLock? je @@led cmp al, 52h ; а может Insert? jne @@unccode ; <- это не регистровая клавиша, просто заносим в буфер xor Status[0], INSERT jmp @@unccode ; <- Insert тоже кладем в буфер @@led: xor Status[1], dh ; переключаем бит клавиши в противоположное состояние call key_SwitchLed jmp @@exit ; сюда попадают все остальные коды расширенной/доп. клавиатуры @@unccode: xor al, al ; обнуляем код ascii-символа jmp @@savecode ; и сохраняем скэн-код в буфер ; пришел код отжатия клавиши (в регистре AL), уточняем какой key_Release: test Status[1], E0 jnz @@isrshift ; <- пропускаем код E0h,AAh(2ah+80h) mov dh, not LSHIFT cmp al, 2ah+80h ; отжат левый Shift? je @@islang @@isrshift: mov dh, not RSHIFT cmp al, 36h+80h ; отжат правый Shift? je @@islang mov dh, not LCONTROL cmp al, 1dh+80h ; отжат Ctrl? je @@isrightup mov dh, not LALT cmp al, 38h+80h ; отжат Alt? jne @@doneup @@isrightup: test Status[1], E0 ; если предыдущий код был E0h jz @@islang ; то это правый Ctrl/Alt rol dh, 1 ; сдвигаем бит влево (делаем клавишу правой) @@islang: ; проверяем на наличие переключения раскладки клавиатуры (Rus/Eng) and al, 7fh ; сбрасываем бит отжатия клавиши cmp al, LangSwitch[0] jne @@saveup mov al, LangSwitch[1] and al, Status[0] cmp al, LangSwitch[1] jne @@saveup xor Status[1], RUSLAT call key_SetBorder ; отмечаем текущий режим цветом бордюра @@saveup: and Status[0], dh ; убираем бит нажатия клавиши @@doneup: retn key_Handler endp ; изменяет регистр букв (строчные/прописные) в регистре AL key_ChangeReg proc cmp al, 'ё' ja @@done cmp al, 'Ё' jb @@next xor al, 1 ret @@next: cmp al, 'р' jae @@50 cmp al, 'п' ja @@done cmp al, 'а' jae @@20 cmp al, 'Р' jae @@50 cmp al, 'А' jae @@20 cmp al, 'z' ja @@done cmp al, 'a' jae @@20 cmp al,'Z' ja @@done cmp al,'A' jae @@20 @@done: ret @@50: xor al, 50h @@20: xor al, 20h ret key_ChangeReg endp ; зажигает/гасит светодиоды key_SwitchLed proc uses ax call key_Wait8042In ; ждем готовности входного буфера клавиатуры mov al, 0edh ; команда установки светодиодов out 60h, al call key_Wait8042In mov al, Status[1] shr al, 8-3 ; выделяем биты, отвечающие за состояние светодиодов out 60h, al ret key_SwitchLed endp ; устанавливает цвет бордюра экрана, в зависимости от текущей раскладки (Rus/Eng) key_SetBorder proc uses dx, ax mov dx, 3dah ; очищаем индекс регистра 3c0h in al, dx mov dx, 3c0h mov al, 31h out dx, al mov al, Status[1] and al, RUSLAT out dx, al ret key_SetBorder endp ; ожидает готовности входного буфера i8042 ; на выходе: ; флаг ZF: 0 - ошибка тайм-аута ; 1 - все ок, буфер свободен key_Wait8042In proc uses cx or cx, -1 ; защита от зацикливания @@waitin: in al, 64h ; читаем регистр состояния клавиатуры test al, 10b ; буфер 8042 свободен? loopnz @@waitin ret key_Wait8042In endp ; ожидает готовности выходного буфера i8042 ; на выходе: ; флаг ZF: 1 - ошибка тайм-аута ; 0 - все ок, буфер свободен key_Wait8042Out proc uses cx or cx, -1 ; защита от зацикливания @@waitout: in al, 64h ; читаем регистр состояния клавиатуры test al, 1b ; буфер 8042 свободен? loopz @@waitout ret key_Wait8042Out endp ; добавляет очередной символ и его скэн-код в очередь буфера драйвера клавиатуры ; на входе: ; al - ASCII символ или 0 ; ah - скэн-код key_Put proc uses bx, dx cli ; нас нельзя прерывать :) mov bx, [buf_tail] mov dx, bx inc dx ; dx - следующая позиция в буфере cmp dx, buf_end ; нет ли выхода за пределы буфера? jb @@isover xor dx, dx ; вышли, переходим в его начало @@isover: shl bx, 1 ; bx = bx*2 cmp dx, [buf_head] ; есть ли место в буфере? je @@overflow mov buf_code[bx], ax; пересылаем в буфер mov [buf_tail], dx ; сохраняем новую позицию sti ret @@overflow: ; произошло переполнение буфера, символ будет потерян sti ret key_Put endp ; чтение символа и его скэн-кода из буфера клавиатуры ; на выходе: ; флаг ZF - 0, если символ считан ; - 1, если буфер пуст ; al - ASCII символ или 0 (при ZF=0) ; ah - скэн-код (при ZF=0) key_Get proc uses bx, dx cli ; нас нельзя прерывать :) mov bx, [buf_head] mov dx, bx cmp bx, [buf_tail] ; буфер пуст? je @@empty ; <-- да ; извлекаем очередной символ и его скэн-код shl bx, 1 ; bx = bx*2 inc dx ; dx - следующая позиция в буфере mov ax, buf_code[bx]; получаем очередной символ и скэн-код cmp dx, buf_end ; нет ли выхода за пределы буфера? jb @@save xor dx, dx ; был, переходим в начало буфера @@save: mov [buf_head], dx or ax, ax ; сбрасываем флаг ZF sti ret @@empty: ; буфер пуст, возвращаем взведенный флаг ZF sti ret key_Get endp ; чтение флагов состояния регистровых клавиш ; на выходе: ; al - флаг регистровых клавиш ; ah - флаг состояния драйвера и светодиодов key_Status proc mov al, Status[0] mov ah, Status[1] ret key_Status endp ; инициализирует внутренние переменные и устанавливает свой обработчик прерываний клавиатуры key_Install proc uses ax, es xor ax, ax mov es, ax ; es - сегмент данных BIOS ; инициализируем переменные mov Status[0], al mov Status[1], al mov [buf_head], ax mov [buf_tail], ax ; сохраняем статус светодиодов mov al, es: [417h] mov [oldLed], al ; гасим светодиоды call key_SwitchLed ; сохраняем прежний вектор обработчика прерывания клавиатуры cli mov ax, es: [9*4] ; IRQ 1 аппаратно накладывается на 9-й вектор mov [oldOfsIRQ1], ax mov ax, es: [9*4+2] mov [oldSegIRQ1], ax ; устанавливаем свой mov ax, offset key_Handler mov es: [9*4], ax mov es: [9*4+2], cs sti ret key_Install endp ; восстанавливает прежний обработчик прерываний клавиатуры key_Uninstall proc uses ax, es ; восстанавливаем IRQ cli xor ax, ax mov es, ax ; es - сегмент данных BIOS mov ax, [oldOfsIRQ1] mov es: [9*4], ax mov ax, [oldSegIRQ1] mov es: [9*4+2], ax sti ; восстанавливаем состояние светодиодов mov al, [oldLed] shl al, 1 mov Status[1], al call key_SwitchLed ret key_Uninstall endp end Компилируется так: tasm /m3 key.asm В аттаче таблица скан-кодов, а так же небольшой пример - установка временных характеристик клавиатуры. Особая благодарность 7in X, за предоставленные сорсы, которые навели на пару интересных идей, по реализации драйвера. Прикреплённый файлKEYDRV.ZIP (14.86 Кбайт, скачиваний: 708) |
Сообщ.
#2
,
|
|
|
Добрый день!
Пытаюсь подключить вышеприведенный обработчик в программу на Паскале, но, то Паскаль ругается, то tasm... Мне кажется, что это из-за модели памяти TINY в модулях на асме. И там нужно указать LARGE. Но если изменить модель памяти, то tasm начинает ругаться: (первые 4 ошибки - указывают на место описания таблицы перекодировки скан кодов) **Error** key.asm(44) CS unreachable from current segment **Error** key.asm(52) CS unreachable from current segment **Error** key.asm(60) CS unreachable from current segment **Error** key.asm(68) CS unreachable from current segment **Error** key.asm(139) Relative jump out of range by O002h bytes **Error** key.asm(146) Relative jump out of range by O002h bytes (последние две - на джампы в процедуре key_Handler) Подскажите, пожалуйста, что нужно поменять в коде обработчика, если изменять модель памяти на large? Или может нужно в Паскале по-другому объявлять процедуры? (я пробовал указывать near; - не помогло... Покажите, пожалуйста, как нужно правильно объявить в Паскале все PUBLIC процедуры из этого обработчика...) |