е кодов ASCII набор операторов IF...THEN сразу начинает проверять какая клавиша была нажата, отсылая программу на соответствующую проце- дуру. В случае 2-байтных кодов управление передается отдельной процедуре. В этой процедуре функция RIGHT$ убирает левый символ, который просто равен нулю и только отмечает расширенный код. Затем используется функция ASC для преобразования строки из сим- вольной формы в числовую. И, наконец, вторая серия операторов IF...THEN проверяет получившееся число на соответствующие Alt-A, Alt-B и т.д. 100 C$ = INKEY$:IF C$="" THEN 100 'ожидаем нажатия клавиши 110 IF LEN(C$)=2 THEN 500 'если расш. код - на 500 120 IF C$="a" OR C$="A" THEN GOSUB 1100 'это A? 130 IF C$="b" OR C$="B" THEN GOSUB 1200 'это B? 140 IF C$="c" OR C$="C" THEN GOSUB 1300 'это C? . . 500 C$=RIGHT$(C$,1) 'получаем второй байт расш. кода 510 C=ASC(C$) 'преобразуем его в число 520 IF C=30 THEN GOSUB 2100 'это Alt-A? 530 IF C=48 THEN GOSUB 2200 'Alt-B? 540 IF C=46 THEN GOSUB 2300 'Alt-C? Отметим, что в строке 120 (и последующих) можно также использо- вать числовые значения кодов ASCII: 120 IF C=97 OR C=65 THEN GOSUB 1100 Конечно надо сначала преобразовать C$ в форму целого числа, как это сделано в строке 510. В программах, в которых требуется длин- ная цепочка таких операторов, можно сэкономить место, изменяя C таким образом, чтобы она всегда соответствовала либо верхнему, либо нижнему регистру. Сначала нужно только проверить, что код ASCII C$ находится в правильном диапазоне. Затем установить, меньше ли этот код 91, тогда мы имеем дело с символом верхнего регистра. Если это так, то надо для перевода в нижний регистр добавить 32. В противном случае, оставить все как есть. После этого будет достаточно более короткого оператора, такого как IF C=97 THEN ... Вот код этой процедуры: 500 C=ASC(C$) 'получаем ASCII код символа 510 IF NOT ((C>64 AND C<91)OR(C>96 AND C<123)) THEN ... 520 IF C<91 THEN C=C+32 'приводим все к нижнему регистру 530 IF C=97 THEN ... '... начинаем проверку значений Средний уровень. Функции 7 и 8 прерывания 21H ожидают ввода символа, если буфер клавиатуры пуст, а когда он появляется, то не выводится на экран. При этом функция 8 определяет Ctrl-Break (и инициирует процедуру обработки Ctrl-Break[3.2.8]), а функция 7 не реагирует на него. В обоих случаях символ возвращается в AL. Когда AL содержит ASCII 0, то получен расширенный код. Повторите прерывание и в AL поя- вится второй байт расширенного кода. ;---получаем введенный символ MOV AH,7 ;номер функции INT 21H ;ожидаем ввод символа CMP AL,0 ;проверка на расширенный код JE EXTENDED_CODE ;если да, то на особую процедуру . ;иначе, код символа в AL ;---процедура обработки расширенных кодов EXTENDED_CODE: INT 21H ;берем второй байт кода CMP AL,75 ;проверяем на "стрелку-влево" JNE C_R ;если нет, то след. проверка JMP CURSOR_LEFT;если да, то на процедуру C_R: CMP AL,77 ;сравниваем дальше и т.д. BIOS обеспечивает процедуру, которая предоставляет те же воз- можности, что и функции MS DOS. Поместите 0 в AH и вызовите пре- рывание 16H. Функция ожидает ввода символа и возвращает его в AL. В этом случае и расширенные коды обрабатываются за одно прерыва- ние. Если в AL содержится 0, то в AH будет содержаться номер расширенного кода. При это не обрабатывается Ctrl-Break. ;---ждем нажатия клавиши MOV AH,0 ;номер функции ожидания ввода INT 16H ;получаем введенный код CMP AL,0 ;проверка на расширенный код JE EXTENDED_CODE ;если да, то на спец. процедуру . ;иначе символ в AL ;---процедура обработки расширенного кода EXTENDED_CODE: CMP AH,75 ;берем расширенный код из AH ;и т.д. 3.1.4 Ожидание нажатия клавиши и эхо на экран. При вводе данных и текста, эхо вводимых символов обычно вы- дается на экран. При этом такие символы как возврат каретки или забой переводятся в соответствующие перемещения курсора, а не изображаются как ASCII символы для этих кодов. Выдача эха проис- ходит в той позиции, где предварительно был установлен курсор и текст автоматически переносится на следующую строку при достиже- нии конца текущей. Перенос на следующую строку не требует спе- циального кода, поскольку символы помещаются в следующую позицию буферной памяти дисплея, которая представляет из себя одну длин- ную строку, включающую все 25 строк дисплея. Высокий уровень. В Бейсике надо перехватить введенный символ с помощью операто- ра INKEY$, как показано в [3.1.3]. Затем его надо вывести на экран, прежде чем получать таким же способом следующий. Для выво- да можно использовать либо оператор PRINT, либо оператором POKE прямо поместить символ в видеобуфер, используя отображение в память, как показано в [4.3.1] (буфер начинается с сегмента памя- ти &HB000 для монохромного адаптера и с &HB800 - для цветного адаптера). При использовании PRINT не забудьте поставить в конце двоеточие, иначе будет автоматически добавлен код возврата карет- ки. Ниже приведены примеры использования обоих методов. При этом не проводится никакого анализа на управляющие символы. Вводимые символы формируются в виде строки данных в переменной KEYSTROKE$. 100 ' метод использующий PRINT 110 LOCATE 10,40 'установка курсора в позицию 10,40 120 KEYSTROKE$="" 'очистка переменной 130 C$=INKEY$:IF C$="" THEN 130 'ожидание ввода символа 140 KEYSTROKE$=KEYSTROKE$ + C$ 'запись его в переменную 150 PRINT C$; 'печать символа 160 GOTO 130 'прием следующего символа 100 ' метод использующий POKE 110 DEF SEG = &HB000 'установка сегмента на видеобуфер 120 POINTER = 1678 'указатель на позицию 10,40 130 KEYSTROKE$="" 'очистка переменной 140 C$=INKEY$:IF C$="" THEN 140 'ожидание ввода символа 150 KEYSTROKE$=KEYSTROKE$ + C$ 'запись его в переменную 160 POKE POINTER,ASC(C$) 'помещение символа в видеобуфер 170 POINTER=POINTER + 2 'сдвиг указателя на следующий символ 180 GOTO 140 'прием следующего символа Средний уровень. Функция 1 прерывания 21H ожидает ввода символа, если буфер клавиатуры пуст, а затем выводит его на экран в текущую позицию курсора. Обрабатывается Ctrl-Break, поэтому может выполняться процедура обработки Ctrl-Break [3.2.8]. Введенный символ возвра- щается в AL. При вводе расширенного кода AL содержит ASCII 0. Для получения в AL второго байта расширенного кода надо повторить прерывание. ;---получение введенного символа MOV AH,1 ;номер функции INT 21H ;ожидаем нажатия клавиши CMP AL,0 ;расширенный код? JE EXTENDED_CODE ;если да, то на спец. процедуру . ;иначе символ находится в AL ;---процедура обработки расширенных кодов INT 21H ;получаем в AL номер кода CMP AL,77 ;проверка на "курсор-вправо" JNE C_R ;если нет, проверка следующего JMP CURSOR_RIGHT ;если да, то на процедуру C_R: CMP AL,75 ;... и т.д. Эта функция полностью игнорирует клавишу <ESC>. Клавиша табу- ляции интерпретируется нормально. Клавиша забой сдвигает курсор на одну позицию влево, но символ, находящийся в этой позиции не стирается. Клавиша <Enter> вызывает перемещение курсора в первую позицию текущей строки (нет автоматического перевода строки). 3.1.5 Прием символа без ожидания. Некоторые программы, работающие в реальном времени не могут останавливаться и ждать нажатия клавиши; они принимают символ из буфера клавиатуры только в те моменты, когда это удобно для прог- раммы. Например, бездействие процессора во время ожидания ввода с клавиатуры остановило бы все действия на экране в игровой прог- рамме. Напомним, что легко проверить пуст или нет буфер клавиату- ры, используя методы, описанные в [3.1.2]. Высокий уровень. Надо просто использовать INKEY$, не помещая его в цикл: 100 C$=INKEY$ 'получение символа 110 IF C$ <> "" THEN...'если символ введен, то ... 120 ... 'иначе нет символа в буфере Средний уровень. Функция 6 прерывания 21H - это единственный способ получить введенный символ без ожидания. Эта функция не дает эха на экран и не распознает Ctrl-Break. Перед вызовом прерывания в DL должно быть помещено 0FFH. В противном случае функция 6 служит совершен- но противоположной цели - печатает в текущей позиции курсора символ, находящийся в DL. Флаг нуля устанавливается в 1, если буфер клавиатуры пуст. Если символ принят, то он помещается в AL. Код ASCII 0 индицирует расширенный код и для получения номера кода прерывание должно быть повторено. MOV AH,6 ;номер функции DOS MOV DL,0FFH ;запрос ввода с клавиатуры INT 21H ;получение символа JZ NO_CHAR ;переход если нет символа CMP AL,0 ;проверка на расширенный код JE EXTENDED_CODE ;если да, то на спец. процедуру ... ;иначе в AL код ASCII EXTENDED_CODE: INT 21H ;получаем номер расширенного кода ... ;номер кода в AL 3.1.6 Получение строки символов. И Бейсик и MS DOS предоставляют процедуры для приема строки символов. Они автоматически повторяют процедуры ввода одного символа, описанные в предыдущих разделах, ожидая ввода возврата каретки, сигнализирующего окончание строки. Конечно должна быть отведена память, достаточная для приема всех символов строки, и должна записываться длина каждой строки для того, чтобы отделить одну строку от другой. Это делается с помощью дескрипторов стро- ки, которые состоят из одного или более байтов, содержащих адрес и/или длину строки. В Бейсике первые два байта дескриптора строки содержат адрес строки, а сами дескрипторы хранятся в массиве отдельно от строк. Длина строки хранится в третьем байте 3-байт- ного дескриптора. С другой стороны, DOS хранит длину строки прямо в начале самой строки и для программы достаточно знать положение строки в памяти. Высокий уровень. Бейсик может принимать с и без автоматического эха на экране. Более просто делается ввод с эхом, так как он выполняется встроенной функцией ввода строки INPUT. INPUT автоматически соби- рает вводимые символы, выводя их на экран по мере получения. При нажатии клавиши <Enter> ввод завершается и значение строки прис- ваивается указанной переменной (посылаемый клавишей <Enter> код ASCII 13 не добавляется к строке). INPUT допускает возможность редактирования строки, предоставляемую DOS, поэтому опечатки могут быть исправлены перед вводом строки. INPUT принимает числа в ввиде строки и автоматически преобразует их в числовую форму, если для ввода будет указано имя числовой переменной. Наконец, INPUT может выдавать на экран строку, запрашивающую пользователя о требуемой информации. Такая строка может быть длиной до 254 символов. Если ее длина больше, то лишние символы игнорируются. Основная форма этого оператора INPUT "запрос", имя_переменной. Полное описание этого опертора см. в руководстве по Бейсику. 110 INPUT "Enter your name: ",NAME$ 'принимает имя как строку 120 INPUT "Enter your age: ",AGE% 'принимает возраст как число Оператор INPUT неадекватен, когда в вводимой строке могут встречаться расширенные коды, такие как коды управления курсором в полноэкранном текстовом редакторе. В этом случае требуется использовать функцию INKEY$ для приема каждого символа, затем проверять ввод на расширенные коды, выделять символы управления курсором, такие как возврат каретки, и только после этого выво- дить на экран те символы, которые следует выводить. При этом управляющие символы также включаются по одному в конец строковой переменной. Текстовые файлы представляют собой набор таких стро- ковых переменных. В пункте [3.1.8] Вы найдете процедуру ввода с клавиатуры, в которой функция INKEY$ испоьзуется указанным обра- зом. Средний уровень. Функция 0AH прерывания 21H позволяет вводить строку длиной до 254 символов, выдавая эхо на терминал. Эта процедура продолжает ввод поступающих символов до тех пор, пока не нажата клавиша возврат каретки. DS:DX указывает на адрес памяти, куда должна быть помещена строка. При входе первый байт в этой позиции должен содержать число байтов, отводимых для этой строки. После того как строка введена, второй байт даст число реально введенных симво- лов. Сама строка начинается с третьего байта. Надо отвести достаточно памяти для строки нужной длины плюс два байта для дескриптора строки и один добавочный байт для возв- рата каретки. Когда Вы устанавливаете максимальную длину строки в первом байте, то не забудьте добавить 1 для возврата каретки. Код возврата каретки - ASCII 13 - вводится как последний символ стро- ки, но он не учитывается в результате, который функция помещает во второй байт дескриптора строки. Таким образом, для получения 50-символьной строки надо отвести 53 байта памяти и поместить в первый байт ASCII 51. После ввода 50 символов второй байт будет содержать ASCII 50, а 53-й байт отведенной памяти - ASCII 13. ;---в сегменте данных STRING DB 53 DUP(?) ;область для строки 50 символов ;---получение строки с клавиатуры LEA DX,STRING ;DS:DX указывают на адрес строки MOV BX,DX ;пусть BX тоже указывает на строку MOV AL,51 ;установка длины строки (+1 для CR) MOV [BX],AL ;посылаем в 1-й байт дескриптора MOV AH,0AH ;номер функции INT 21H ;получаем строку ;---проверка длины строки MOV AH,[BX]+1 ;теперь длина в AH В этой процедуре можно использовать возможности редактирования строки MS DOS. Нажатие клавиши забой или "стрелка-влево" удаляет символ с экрана, а также не помещает его в память. Работает кла- виша табуляции, расширенные коды игнорируются, пустые строки допускаются (имеется ввиду возврат каретки, которому не предшест- вует другого символа). На терминале при достижении правого края строка переносится на следующую строку, а при достижении правого нижнего угла экран сдвигается на строку вверх. Когда вводится больше символов, чем отведено места для строки, то лишние символы игнорируются и включается гудок динамика. MS DOS обеспечивает и другой способ получения строки, при котором не выводится эхо на терминал. Функция 3FH прерывания 21H - это функция ввода общего назначения, которая чаще всего исполь- зуется при дисковых операциях. Она требует предопределенного дескриптора файла (file handle), который является кодовым числом, используемым операционной системой для обозначения устройства ввода/вывода. Для клавитуры используется дескриптор 0 и он должен быть помещен в BX. Поместите в DS:DX адрес, по которому должна находиться строка, а в CX - максимальную длину строки и вызовите функцию: ;---чтение строки без эха MOV AH,3FH ;номер функции MOV BX,0 ;номер дескриптора файла LEA DX,STRING_BUFFER ;указатель на буфер ввода строки MOV CX,100 ;максимальная длина строки INT 21H ;ждем ввода Ввод строки завершается нажатием клавиши возврат каретки и DOS добавляет в конец строки два символа: возврат каретки и перевод строки (ASCII 13 и ASCII 10). Из-за этих добавочных символов, при указании длины строки 100 символов она может занимать до 102 байт памяти. Длина введенной строки возвращается в AX и это значение включает два символа-ограничителя. 3.1.7 Проверка/установка статуса клавиш-переключателей. Два байта, расположенные в ячейках памяти 0040:0017 и 0040:0018 содержат биты, отражающие статус клавиши сдвига и дру- гих клавиш-переключателей следующим образом: Бит Клавиша Значение, когда бит = 1 0040:0017 7 Insert режим вставки включен 6 CapsLock режим CapsLock включен 5 NumLock режим NumLock включен 4 ScrollLock режим ScrollLock включен 3 Alt клавиша нажата 2 Ctrl клавиша нажата 1 левый Shift клавиша нажата 0 правый Shift клавиша нажата 0040:0018 7 Insert клавиша нажата 6 CapsLock клавиша нажата 5 NumLock клавиша нажата 4 ScrollLock клавиша нажата 3 Ctrl-NumLock режим Ctrl-NumLock включен остальные биты не используются Прерывание клавиатуры немедленно обновляет эти биты статуса, как только будет нажата одна из клавиш-переключателей, даже если не было считано ни одного символа из буфера клавиатуры. Это верно и для клавиши Ins, которая единственная из этих 8 клавиш помещает код в буфер (установка статуса Ins меняется даже если в буфере нет места для символа). Отметим, что бит 3 по адресу 0040:0018 устанавливается в 1, когда действует режим задержки Ctrl-NumLock; поскольку в этом состоянии программа приостановлена, то этот бит несущественен. Прерывание клавиатуры проверяет состояние статусных битов перед тем, как интерпретировать нажатые клавиши, поэтому когда программа меняет один из этих битов, то эффект такой же, как при физическом нажатии соответствующей клавиши. Вы можете захотеть установить состояние клавиш NumLock и CapsLock, чтобы быть уве- ренным, что ввод будет требуемого вида. Наоборот, Ваша программа может нуждаться в чтении статуса этих клавиш, например для того, чтобы вывести текущий статус на экран. Отметим, что клавиатура AT правильно устанавливает световые индикаторы состояния клавиш, даже если переключены программно. Высокий уровень. В данном примере клавиша NumLock переводится в режим, когда клавиши дополнительной клавиатуры используются для перемещения курсора, за счет сбрасывания бита 5 по адресу 0040:0017 в 0. Это достигается за счет операции логического "И" значения, располо- женного по этому адресу с числом 223 (цепочка битов 11011111B - описание логики битовых операций см. в Приложении Б). Результат помещается в байт статуса. В примере затем восстанавливается значение этого бита в 1, за счет логического "ИЛИ" с 32 (00100000B). 100 DEF SEG = &H40 'устанавливаем сегмент на область 110 STATUSBYTE=PEEK(&H17) 'BIOS и берем байт статуса 120 NEWBYTE=STATUSBYTE AND 223 'обнуляем бит 5 130 POKE(&H17,NEWBYTE) 'посылаем новое значение статуса Чтобы, наоборот, включить этот бит: 120 NEWBYTE=STATUSBYTE OR 32 'устанавливаем бит 5 130 POKE(&H17,NEWBYTE) 'посылаем новое значение статуса Строки 110-130 могут быть уплотнены к виду: 110 POKE(&H417,PEEK(&H417)AND 223) или 110 POKE(&H417,PEEK(&H417)OR 223) Средний уровень. Функция 2 прерывания 16H предоставляет доступ к одному - но только одному - из байтов статуса. Это байт по адресу 0040:0017, который содержит больше полезной информации. Байт возвращается в AL. ;---проверка статуса клавиши вставки MOV AH,2 ;номер функции INT 16H ;получаем байт статуса TEST AL,10000000B ;проверяем бит 7 JZ INSERT_OFF ;если 0, то INSERT выключен Низкий уровень. В данном примере устанавливается режим вставки, за счет уста- новки бита 7 байта статуса по адресу 0040:0017 (который адресует- ся как 0000:0417). SUB AX,AX ;устанавливаем добавочный сегмент на MOV ES,AX ;начало памяти MOV AL,10000000B ;готовим бит 7 к установке OR ES:[417H],AL ;меняем байт статуса 3.1.8 Написание процедуры ввода с клавиатуры общего назначения. Система кодов, используемых клавиатурой, не поддается простой интрепретации. Коды могут иметь длину 1 или 2 байта и нет просто- го соответствия между длиной кода и тем, служит ли он для обозна- чения символа или для управления оборудованием. Не все комбинации клавиш даже выдают уникальный код, поэтому необходимы добавочные усилия, чтобы различить их. Ни коды ASCII, ни расширенные коды не упорядочены таким образом, который бы позволил их простую группи- ровку и проверку ошибок. Другими словами, процедура ввода с кла- виатуры общего назначения требует хлопотливого программирования. Здесь приведены примеры на Бейсике и с использованием прерыва- ния 16H. В них показано как свести вместе большинство информации, приведенной в данной главе. Общий алгоритм показан на рис. 3-3. Высокий уровень. Процедура обработки ввода с клавиатуры, написанная на Бейсике, может делать все что делает ассемблерная процедура, за одним исключением. Функция INKEY$ не предоставляет доступа к скан-ко- дам. Это означает, что Вы не можете сказать получены ли коды ASCII 8, 9, 13 и 27 от нажатия клавиш <BackSpace>, <Tab>, <Enter> и <Escape> или через Ctrl-H, -I, -M и -[. Различие может быть установлено проверкой бита статуса клавиши Ctrl, по адресу 0040:0017, в момент нажатия клавиши. Но этот метод не будет рабо- тать, если введенный символ был запасен в буфере клавиатуры в течение некоторого времени. 100 C$=INKEY$:IF C$="" THEN 100 'получение символа 110 IF LEN(C$)=2 THEN 700 'если расширенный, то на 700 120 C=ASC(C$) 'иначе берем номер кода ASCII 130 IF C<32 THEN 300 'если управляющий, то на 300 140 IF C<65 OR C>123 THEN 100 'принимаем только символы 150 '''пишущей машинки и делаем с ними, что хотим, например: 160 S$=S$+C$ 'добавляем символ к строке 170 PRINT C$; 'выводим его на экран 180 '''... и т.д. 190 GOTO 100 'на ввод следующего символа . . 300 '''процедура обработки управляющих кодов ASCII 310 DEF SEG = 0 'указываем на начало памяти 320 REGISTER=PEEK(&H417) 'берем регистр статуса 330 X=REGISTER AND 4 'X=4, когда нажат Ctrl 340 IF X=0 THEN 500 'если не нажат, то на 500 350 '''если это комбинация Ctrl-буква, то делаем что хотим 360 IF C=8 THEN GOSUB 12000 'например, переходим на проце- 370 '''дуру вывода экрана помощи и т.д. 380 GOTO 100 'на ввод следующего символа . . 500 '''процедура обработки 4-х клавиш: декодирует коды ASCII 8, 510 '''9, 13 и 27, когда клавиша Ctrl не нажата 520 IF C=8 THEN GOSUB 5000 'обработка <BackSpace> 530 IF C=9 THEN GOSUB 6000 'обработка <Tab> 540 IF C=13 THEN GOSUB 7000 'обработка <CR> 550 IF C=27 THEN GOSUB 8000 'обработка <Esc> 560 GOTO 100 'на ввод следующего символа . . 700 '''процедура обработки расширенных кодов 710 C$=RIGHT$(C$,1) 'берем только 2-й байт C$ 720 C=ASC(C$) 'переводим в числовую форму 730 '''в C - расширенный код - делаем с ним, что хотим, например 740 IF C<71 OR C>81 THEN 100 'берем только управление курсором 750 IF C=72 THEN GOSUB 3500 'обработка "курсор-вверх" 760 '''... и т.д. 770 GOTO 100 'на ввод следующего символа Средний уровень. Этот пример отличается от предыдущего методом распознавания четырех частных случаев Ctrl-H, -I, -M и -[. Здесь, когда встает вопрос о том, возник ли указанный код при нажатии одной клавиши, или в комбинации с клавишей Ctrl, проверяется скан-код. Этот метод более правилен, чем проверка бита статуса, так как скан-код запоминается в буфере клавиатуры, а установка бита статуса может быть изменена. ;---получение кода нажатой клавиши и определение его типа NEXT: MOV AH,0 ;функция ввода с клавиатуры BIOS INT 16H ;получаем введенный код CMP AL,0 ;проверка на расширенный код JE EXTENDED_CODE ;если да, то на спец. процедуру CMP AL,32 ;проверка на управляющий символ JL CONTROL_CODE ;если да, то на спец. процедуру CMP AL,65 ;если символ не входит в набор пишу- JL NEXT ;щей машинки, то берем следующий CMP AL,123 ; JL NEXT ; ;---теперь обрабатываем символ в AL STOSB ;запоминаем символ по адресу ES:DI MOV AH,2 ;функция вывода символа на экран MOV DL,AL ;помещаем символ в DL перед выводом INT 21H ;выводим его на экран . . JMP NEXT ;переходим к следующему символу ;---анализируем управляющие коды CONTROL_CODE: CMP AL,13 ;код ASCII 13? JNE TAB ;если нет, то след. проверка CMP AH,28 ;иначе проверяем скан-код <CR> JNE C_M ;если нет, то было Ctrl-M CALL CARRIAGE_RET;обработка возврата каретки JMP NEXT ;переход к следующему символу C_M: CALL CTRL_M ;обработка Ctrl-M JMP NEXT ;переход к следующему символу TAB: CMP AL,9 ;проверка на табуляцию... . . CMP AL,10 ;затем проверка других . . REJECT: JMP NEXT ;переход к следующему символу ;---анализ расширенных кодов (2-й байт кода в AH): EXTENDED_CODE: CMP AH,71 ;проверка нижней границы JL REJECT ;если меньше, то след. символ CMP AH,81 ;проверка верхней границы JL REJECT ;если больше, то след. символ ;---AH содержит символ управления курсором, анализируем его: CMP AH,72 ;"курсор-вверх"? JE C_U ;если да, то на процедуру CMP AH,80 ;"курсор-вниз"? JE C_D ;если да, то на процедуру . . C_U: CALL CURSOR_UP ;вызов соответствующей процедуры JMP NEXT ;переход к следующему символу C_D: CALL CURSOR_DOWN ;вызов соответствующей процедуры JMP NEXT ;переход к следующему символу 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 Раздел 2. Доступ к отдельным клавишам. Процедура обработки нажатия клавиши должна проверять массу различных типов клавиш и условий, поскольку как одно-, так и двухбайтные коды могут появляться в комбинации с клавишами-перек- лючателями. Не все клавиши логически сгруппированы, по типу кода, который им соответствует. Например, клавиша <Backspace> генери- рует однобайтный код ASCII, а клавиша <Delete> - двухбайтный расширенный код. Клавиша Ctlr генерирует однобайтный код, когда она используется в сочетании с алфавитными клавишами и двухбайт- ный код в остальных случаях. Эти нерегулярности вознмкают из-за ограниченности набора ASCII: прерывание клавиатуры следует согла- шениям ASCII, когда возможно, но когда это невозможно выдает свои (расширенные) коды. В данном разделе перечислены различные группы клавиш, даны их коды и указаны встречающиеся аномалии. В большинстве случаев эта информация доступна в менее удобном виде из таблиц кодов ASCII и расширенных кодов, приведенных в разделе 3 этой главы. Здесь обсуждаются также специальные свойства, приписываемые отдельным клавишам Бейсиком, а также специальная обработка, для интерпрета- ции отдельных клавиш (таких как забой), применяемая в прерываниях DOS. 3.2.1 Использование клавиш <BackSpace>, <Enter>, <Escape> и <Tab>. Клавиши <BackSpace>, <Enter>, <Escape> и <Tab> - единственные четыре несимвольные клавиши, которые генерируют однобайтные ко- ды ASCII. Эти коды содержатся в наборе управляющих кодов [7.1.9], которые занимают первые 32 кода в наборе ASCII. Эти четыре кода могут быть получены также комбинацией буквенных клавиш с клавишей Ctrl: ASCII 8 BackSpace Ctrl + H ASCII 9 Tab Ctrl + I ASCII 13 Enter Ctrl + M ASCII 27 Escape Ctrl + [ В [3.2.2] показано как различать нажатие одной клавиши и комбина- цию с клавишей Ctrl. Отметим, что обратная табуляция, производи- мая нажатием комбинации <Shift> + <Tab>, выдает расширенный код 0;15. Некоторые из прерываний обработки ввода с клавиатуры автомати- чески интерпретируют эти четыре специальных кода. В Бейсике функ- ция INPUT реагирует на <Backspace>, <Tab> и <Enter>. Функция INKEY$ не интерпретирует ни один из управляющих кодов, поскольку у нее нет автоматического эха на экран. Всю работу должна выпол- нять Ваша программа. Напомним, что для управления движением кур- сора Бейсик предоставляет функцию TAB. Из прерываний BIOS и DOS, те которые выдают эхо на терминал интерпретируют также клавиши <BackSpace> и <Tab>. После того как эти коды интерпретируются соответствующим образом, коды ASCII все равно появляются в AL, после чего они могут быть включены в строку символов или игнори- рованы, в зависимости от того, что требуется. 3.2.2 Использование клавиш-переключателей: <Shift>, <Ctrl> и <Alt>. Три типа клавиш-переключателей заставляют только другие клави- ши клавиатуры генерировать различные коды. Как правило, такие комбинации генерируют расширенные коды. Но в двух случаях они дают коды ASCII: (1) когда используется клавиша <Shift> с клавишами алфавитно-цифровых символов и (2) нажатие комбинации клавиш от Ctrl-A до Ctrl-Z дает ASCII коды от 1 до 26. Все остальные комби- нации дают расширенные коды, перечисленные в [3.3.5]. PCjr имеет несколько исключений, которые обсуждаются ниже. Недопустимые комбинации клавиш не производят кода, вообще. За исключением случая специальных комбинаций с Ctrl-Alt, одновремен- ное нажатие нескольких переключателей приводит к тому, что только один из них становится эффективным, причем приоритет у Alt, затем Ctrl, и затем Shift. В [3.1.7] показано как проверить