о надо поместить его код ASCII в соответствующюю позицию второй строки. Например: Позиция символа 1 2 3 4 5 6 7 8 9 10 Код ASCII 205 32 98 111 114 105 110 103 32 205 Код 2-й строки 205 32 32 32 32 32 32 32 32 205 В памяти должна храниться отдельная таблица цепочек битов этих символов для второго прохода. Для двойной вертикальной черты содержимое таблицы для первого прохода будет 0, 255, 0, 255, 0, 0, а для второго - 0, 15, 0, 15, 0, 0. Отметим, что во втором и четвертом байте для второго прохода верхние 4 бита сброшены, чтобы не было надпечатки. Короче, когда начинается печать, то в первую очередь прове- ряется является ли данный символ пседографическим, и если нет, то он посылается на печать, как обычный код ASCII. Во временную строку, используемую для печати второго прохода вставляется про- бел. Затем обрабатывается следующий символ. Когда встречается символ псевдографики, то 6 кодирующих его байтов берутся из таб- лицы, принтер переводится в графический режим для вывода 6 байтов и посылаются данные. Затем принтер автоматически возвращается в текстовый режим. В соответствующую позицию строки для второго прохода помещается код ASCII этого символа псевдографики. Этот процесс продолжается до конца строки, после чего делается прогон бумаги на высоту четырех точек. При повторном проходе надо опять поочередно рассмотреть каждый символ. Если это пробел, то надо печатать символ пробела (т.е. не печатать ничего, а просто прод- винуть головку к следующему символу). Если же это графический символ, то надо найти соответствующий ему данные для второго прохода в отдельной таблице и напечатать его таким же образом, как и при первом проходе. Повторно используйте строку для второго прохода с каждой печатаемой строкой. На рис. 6-3 показана эта процедура. Высокий уровень. В данном примере текст разделен на две колонки, при этом неп- рерывная линия разделяет страницу посредине. Для простоты печа- тается только одна строка, однако этот пример может печатать и целую страницу, если вставить цикл FOR/NEXT в строках 325 и 505. Для демонстрации двух подходов при первом проходе печатается по одному символу, в то время как при втором проходе печатается целая строка. 100 '''таблица данных для первого прохода (только коды 178 и 179) 110 DATA 0, 0, 255, 0, 0, 0 120 DATA 4, 4, 255, 0, 0, 0 130 '''аналогичная таблица для второго прохода 140 DATA 0, 0, 15, 0, 0, 0 150 DATA 0, 0, 15, 0, 0, 0 160 '''помещаем первую таблицу в массив 170 DIM FIRSTPASS$(45) 'описываем массив 180 FOR N = 1 TO 2 'заполняем его 190 Y$ = "" 'Y$ хранит 6 байтов на символ 200 FOR M=1 TO 6: READ X: Y$ = Y$+CHR$(X): NEXT 210 FIRSTPASS$(N) = Y$: NEXT 'помещаем в массив 220 '''помещаем в массив вторую таблицу 230 DIM SECONDPASS$(45) ' 240 FOR N = 1 TO 2 ' 250 Y$ = "" ' 260 FOR M=1 TO 6: READ X: Y$ = Y$+CHR$(X): NEXT 270 SECONDPASS$(N) = Y$: NEXT ' 280 '''печатаем текст следующей строки 290 TEXT$ = "Here is one column"+CHR$(179)+"Here is the second column" 300 TEMP$ = STRING$(80,32) 'создаем строку для 2-го прохода 310 GRAPH$ = CHR$(27)+CHR$(75)+CHR$(6)+CHR$(0) 320 OPEN "LPT1:" AS #1 'открываем принтер 330 FOR N = 1 TO LEN(TEXT$) 'для каждого символа текста 340 C$ = MID$(TEXT$,N,1) 'берем символ и проверяем его 350 IF C$ < CHR$(128) THEN PRINT #1,C$;: GOTO 400 360 '''предполагаем, что все остальные символы - псевдографика 370 PRINT #1,GRAPH$; 'входим в графический режим 380 PRINT #1,FIRSTPASS$(ASC(C$) - 178);выводим 1-й проход 390 MID$(TEMP$,N) = C$ 'маркер в строке 2-го прохода 400 NEXT 410 '''смещаемся на 8 точек вниз и делаем второй проход 420 PRINT #1,CHR$(27)+CHR$(65)+CHR$(4)+CHR$(141); 430 Z$ = "" 'Z$ содержит строку для 2-го прохода 440 FOR N = 1 TO LEN(TEXT$) 'для каждого символа текста 450 C$ = MID$(TEMP$,N,1) 'берем символ и обрабатываем его 460 IF C$ = CHR$(32) THEN Z$ = Z$+" ": GOTO 480 470 Z$ = Z$+GRAPH$+SECONDPASS$(ASC(C$) - 178) 480 NEXT 490 PRINT #1,Z$ 'печатаем всю строку сразу 500 PRINT #1,CHR$(10); 'добавляем в конце перевод строки Низкий уровень. Программа на ассемблере использует тот же самый алгоритм, что и приведенная программа на Бейсике. Когда используется только несколько символов, то Вы можете сэкономить место, сжав таблицу, с тем чтобы их положение в таблице не было пропорционально их позиции в наборе ASCII. Затем подготовьте небольшую таблицу ин- дексов с помощью инструкции XLAT, с помощью которой можно быстро искать данные в этой таблице. 6.3.5 Копирование экрана на принтер (дамп экрана). Дамп текстового экрана сделать достаточно просто, если все используемые символы содержатся в ПЗУ принтера и ни один из них не выводится со специальными атрибутами, такими как подчеркивание или негативное изображение. В этом простейшем случае программе нужно лишь установить ширину принтера равной 80 символам, а затем считывать символы поочередно из видеобуфера, посылая их как неп- рерывный поток данных на принтер. Если в ПЗУ принтера отсутствуют специальные символы, такие как символы псевдографики, то програм- ма должна подготовить свою таблицу данных для этих символов и выводить их на принтер в графическом режиме. Поскольку эти симво- лы могут заходить в межстрочные интервалы, то может потребоваться специальное программирование [6.3.4]. Каждый из специальных атрибутов символов создает свои пробле- мы. Проверяйте атрибут каждого символа при считывании его из видеобуфера (в [4.1.3] обсуждается значение битов, соответствую- щее различным атрибутам). Когда символ выделен с помощью подчер- кивания или повышенной интенсивности, то надо включать подчерки- вание или печать жирным шрифтом на принтере. Однако если символ выводится в негативном изображении, то возникают те же проблемы, что и с некоторыми графическими символами: область негативного изображения должна простираться до верхнего края следующей стро- ки. В этом случае надо следуя указаниям [6.3.4] заполнить черным всю область при втором проходе. В зависимости от принтера, Вам может понадобиться создать специальную таблицу данных для вывода символов в негативе, поскольку когда они будут печататься, то окружающие точки могут находиться слишком близко одна к другой, затемняя изображаемый символ. В этом случае не может быть и речи о печати в два прохода. Простым решением проблемы с негативным изображением является использование графического режима экрана для вывода текста, а затем сделать дамп графического экрана. Графические дампы создают свои проблемы. Байт данных принтера соответствует восьми вертикальным точкам, в то время как на экра- не байт представляет 8 горизонтальных точек. Поэтому требуется процедура преобразования, показанная на рис. 6-4. Надо сразу получать по 8 байтов памяти экрана, выбирая такие, которые соот- ветствуют области точек 8*8. Затем надо использовать логические операции для перестановки битов, как показано в примерах. Имейте ввиду, что большинство матричных принтеров искажают экранное изображение. Это происходит потому, что они используют масштабный коэффициент 1:1, в то время как экран использует коэф- фициент 5:6 (масштабный коэффициент сравнивает число горизонталь- ных точек на дюйм с числом вертикальных точек на дюйм). Точнее говоря, искажение изображения на самом деле возникает из-за масш- табного коэффициента экрана, поскольку программы должны специаль- но менять данные для изображения, чтобы оно выглядело так, как нам хочется (например, изображение окружности на экране создается выводом на него эллипса). Когда данные с экрана выводятся на принтер, то эти искажение должны обращаться. Некоторые графичес- кие принтеры имеют специальные режимы, в которых можно выводить копию экрана без искажения, а цветной принтер IBM может менять масштабный коэффициент в любом из своих графических режимов. Высокий уровень. Приводимая процедура на Бейсике делает копию текстового экра- на, игнорируя специальные атрибуты: 10 OPEN "LPT1:" AS #1 'открываем принтер 20 DEF SEG = &HB000 'указываем на видеобуфер 30 PRINT #1,CHR$(13) 'сдвигаем головку влево 40 FOR G = 0 TO 3998 STEP 2 'для каждого байта буфера 50 PRINT #1,CHR$(PEEK(G)); 'читаем его и выводим на принтер 60 NEXT 'обрабатываем следующий байт Переброска цепочек битов для графического дампа требует в Бейсике слишком много времени. Поместите в массив (здесь, BYTE$) восемь байтов, отвечающих области экрана 8*8 точек. Создайте второй массив (VERTICAL$) и обнулите его элементы, а затем поочередно перебрасывайте биты элементов этих массивов следующим образом: 500 FOR M = 0 TO 7 'для каждого бита 510 FOR N = 0 TO 7 'для каждого байта 520 X = ASC(BYTES(N)) 'получаем значение байта 530 Y = 2*(7 - M) 'маска для одного включенного бита 540 Z = X AND Y 'проверка этого бита в байте 550 IF Z <> 0 THEN VERTICAL$(M) = CHR$(ASC(VERTICAL$(M) OR 2*N) 'если он включен, то устанавливаем бит 'в соответствующей позиции 2-го массива 560 NEXT N 'следующий бит 570 NEXT M 'следующий байт Низкий уровень. Язык ассемблера делает битовые преобразования намного быстрее. Вот процедура, которая делает эти преобразования ужасно быстро, поскольку она держит все в микропроцессоре (она немного великова- та, но Вы можете использовать взамен алгоритм, показанный в Бей- сике). Процедура работает, храня 8 результирующих байтов в ре- гистрах CX, DX, BP и DI. Байт экранных данных помещается в AL, а затем в AH передвигаются последовательно CL, CH, DL и DH. Каждый раз из AL в AH сдвигается один бит и когда сделаны 4 сдвига, то CX и DX обмениваются с DX и BP, после чего все это повторяется снова. Этот процесс повторяется для каждого из 8-ми экранных байтов и когда он завершен, то преобразованное изображение хра- нится в регистрах микропроцессора, причем самый левый байт данных для печати в CL. Содержимое регистров выводится на принтер и обнуляется, после чего процесс повторяется для следующих восьми байтов экрана. Сначала получите 8 байтов из видеобуфера и помес- тите их в буфер с именем BUFFER. Поместите 0 в AX, CX, DX, BP и DI. Затем: LEA BX,BUFFER ;указываем на буфер видеоданных MOV SI,0 ;смещение в этом буфере GET_BYTE: MOV AL,[BX][SI] ;берем байт DO_HALF: XCNG AH,CL ;получаем CL, CH, DL и DH SHL AX,1 ;сдвигая бит из AL XCNG AH,CL ; XCNG AH,CH ; SHL AX,1 ; XCNG AH,CH ; XCNG AH,DL ; SHL AX,1 ; XCNG AH,DL ; XCNG AH,DH ; SHL AX,1 ; XCNG AH,DH ; ;---начинаем вторую половину перемещения битов XCNG CX,BP ;обмениваем содержимое CX и DX XCNG DX,DI ; CMP SI,7 ;если все байты преобразованы, то печатаем JE PRINT_BYTES ; INC SI ;иначе переходим к следующему байту JMP SHORT GET_BYTE ; ;---печатаем байты PRINT_BYTES: PUSH DX ;сохроаняем DX MOV AH,5 ;функция вывода на принтер MOV DL,27 ;код Esc INT 21H ;посылаем его MOV DL,75 ;код графического режима INT 21H ;посылаем его MOV DL,6 ;будет послано 6 байтов INT 21H ; MOV DL,0 ; INT 21H ; CALL PRINT_2_BYTES ;посылаем содержимое CX POP CX ; CALL PRINT_2_BYTES ;посылаем содержимое DX MOV CX,BP ; CALL PRINT_2_BYTES ;посылаем содержимое BP MOV DX,DI ; CALL PRINT_2_BYTES ;посылаем содержимое DI . (идем к следующей группе из восьми байтов) . PRINT_2_BYTES: PROC NEAR MOV AH,5 ;функция печати MOV DL,CL ;сначала CL INT 21H ;печатаем MOV DL,CH ;затем CH INT 21H ;печатаем RET PRINT_2_BYTES ENDP Глава 7. Ввод/вывод. Раздел 1. Доступ к последовательному порту. При асинхронной связи машина посылает или принимает байты информации по одному биту. Временные интервалы между байтами при этом несущественны, но времена между отдельными битами байта очень важны. Сигнал на линии может быть высокого или низкого уровня, что соответствует логическим нулю и единице, и говорят, что линия отмечена (marking), когда уровень высокий, и пустая (spacing), когда уровень низкий. Линия поддерживается в отмеченном состоянии, когда по ней нет передачи данных. При начале передачи байта данных сигнал падает в 0, отмечая стартовый бит. Затем следуют восемь битов данных (иногда меньше) в виде набора высоких и низких уровней. Последний бит данных может сопровождаться битом четности, используемым для обнаружения ошибок, а затем в последовательность включаются 1 или более стоп-битов, которым соответствует высокий уровень. Эти стоп-биты начинают отмеченное состояние, которое будет сохранять- ся до тех пор, пока не начнется передача следующего байта данных; число используемых стоп-битов существенно, поскольку они устанав- ливают минимальное время, которое должно пройти перед следующим стартовым битом. На рис. 7-1 показана эта последовательность. Конечно, передающая и приемная станции должны использовать один и тот же протокол для этих цепочек битов и они должны рабо- тать с одной и той же скоростью обмена (измеряемой в битах в секунду, называемых также бодами). При обмене могут легко возни- кать ошибки, поэтому коммуникационное оборудование предоставляет разнообразную информацию о статусе как самого порта, так и при- соединенного к нему модема. Задачей модема является преобразова- ние сигнала, генерируемого портом коммуникации, в акустический сигнал, который может затем быть передан по телефонному каналу. Большинство модемов предоставляют также дополнительные коммуника- ционные возможности, такие как автоматический вызов и ответ, которые не поддерживаются самим портом коммуникации. 7.1.1 Программирование микросхемы UART 8250. Последовательная связь настолько сложна, что были разработаны специальные микросхемы, выполняющие работу по формированию и синхронизации строк битов, составляющих последовательные данные. Такие микросхемы называют универсальным асинхронным приемни- ком-передатчиком (universal asynchronous receiver transmitter или UART). IBM PC использует UART 8250 фирмы Intel. Операционная система поддерживает 2 порта коммуникации, поэто- му в машине имеются 2 микросхемы. Их базовые адреса хранятся в ячейке 0040:0000 для COM1 и 0040:0002 для COM2. (Базовый адрес это 2-хбайтовый адрес порта, который является младшим из группы адресов портов, дающих доступ к UART.) На всех машинах кроме PCjr COM1 имеет базовый адрес 3F8H, а COM2 - 2F8H; PCjr имеет свой внутренний модем по адресу 3F8H, а COM1 - по адресу 3F8H. Для удобства, мы в дальнейшем будем всегда нумеровать регистры 3FxH, но все сказанное в равной степени применимо и к регистрам 2FxH. Микросхема 8250 имеет 10 программируемых однобайтных регист- ров, с помощью которых управляется и контролируется порт коммуни- кации. Большинство из них занимаются инициализацией порта, про- цессом, который может быть очень сложным. Доступ к этим 10 ре- гистрам осуществляется через семь адресов портов с номерами 3F8H - 3FEH (или 2F8H - 2FEH). В пяти случаях регистр, к которому получаем доступ через данный порт, зависит от того, как установ- лен бит 7 в регистре контроля линии, который является единствен- ным регистром с адресом порта 3FBH. Вот эти регистры: 3F8H (OUT, бит 7 = 0 в 3FBH) Регистр хранения передатчика 3F8H (IN, бит 7 = 0 в 3FBH) Регистр данных приемника 3F8H (OUT, бит 7 = 1 в 3FBH) Делитель скорости обмена (младший) 3F9H (IN, бит 7 = 1 в 3FBH) Делитель скорости обмена (старший) 3F9H (OUT, бит 7 = 0 в 3FBH) Регистр разрешения прерывания 3FAH (IN) Регистр идентификации прерывания 3FBH (OUT) Регистр управления линии 3FCH (OUT) Регистр управления модемом 3FDH (IN) Регистр статуса линии 3FEH (IN) Регистр статуса модема Из десяти регистров только шесть необходимы для простой после- довательной связи. Регистр хранения передатчика содержит байт данных, которые будут посланы [7.1.6], а регистр данных приемника - последний полученный байт данных [7.1.7]. Регистры управления и статуса линии инициализируют и управляют линией связи, используя скорость обмена, содержащуюся в двух регистрах делителя скорости обмена [7.1.2]. Из оставшихся четырех регистров регистры управле- ния и статуса модема используются только для связи через модем [7.1.5], а два регистра, связанных с прерываниями используются только в процедурах, управляемых прерываниями [7.1.8]. Прерывания используются при связи в целях эффективности. Обыч- ная коммуникационная процедура непрерывно проверяет регистр ста- туса линии, ожидая вводимого символа или указаниия, что все гото- во для передачи следующего байта данных. Поскольку процессор намного быстрее, чем обычные скорости с которыми передаются пос- ледовательные данные, то этот метод напрасно расходует процессор- ное время, которое может использоваться для обработки поступающих или передаваемых данных. По этой причине микросхема 8250 может быть установлена в режим, вызывающий прерывание при появлении символа, возникновении ошибки и т.п. Это прерывание моментально вызовет процедуру Вашей программы, которая, скажем, будет переда- вать следующий символ из коммуникационного буфера. 7.1.2 Инициализация последовательного порта. При инициализации порта коммуникации ("открытии") устанавли- ваются все его параметры. Эти параметры длину слова, число стоп-битов, установку четности и скорость обмена. Длина слова это число битов, которое образует основную единицу данных. Если мы работаем с привычными порциями по 8 битов, то 7 битов достаточны для стандартных файлов ASCII (в которых все символы имеют коды, не превышающие ASCII 128), в то время как для передачи численных данных достаточно порций по 4 бита. Высокий уровень. Бейсик открывает коммуникационный канал как файл, и как тако- вому ему должен быть присвоен идентификационный номер: OPEN "COM1: .........." AS #1 В кавычках должна быть помещена вся информация, необходимая для инициализации порта коммуникации, при этом каждый элемент отде- ляется от предыдущего запятой. Инициализационные данные всегда вводятся в следующем порядке: Скорость обмена дается как целое число: 75, 100, 150, 300, 600, 1200, 1800, 2400, 4800 или 9600 бод. По умолча- нию берется скорость обмена 300 бод. Четность вводится как односимвольный код: O для нечетной E - для четной и N - при отсутствии контроля по четности. Могут быть также S - когда бит чет- ности всегда равен 0 и M - когда бит четности всегда равен 1. Если используются 8 бит данных, то надо указывать N; при использовании четырех бит не надо использовать N. По умолчанию - E. Биты данных дается как целое число 4, 5, 6, 7 или 8. По умолчанию берется 7. Стоп-биты дается как целое число 1 или 2, причем 2 - значение по умолчанию для 75 и 110 бод, а 1 - для остальных. Когда число битов данных равно 4 или 5, то 2 обозначает 1 1/2 стоп-бита. Такое значение возможно при коммуникации, так как в этом случае бит является единицей времени и поэтому делим. Оператор OPEN "COM1:" AS #1 открывает COM1 для связи со скоростью 300 бод с четной четностью, используя 7 битов данных и 1 стоп-бит. OPEN "COM1:1200,O,8,1" устанавливает скорость 1200 бод, нечетную четность, 8 бит на символ и 1 стоп-бит. Отметим, что Вы можете завершить оператор OPEN выражением LEN = число, где число устанавливает максимальный размер блока, с которым операторы GET и PUT могут обрабатывать данные (по умолчанию 128 байтов). Имеет- ся также ряд команд управления модемом, которые также могут быть включены в эту спецификацию. (В [7.1.5] объясняется специальная терминология, используемая при этом): RS Подавляет сигнал "Запрос на посылку" (Request to send). Если эта команда опущена, то OPEN "COM" включает RTS. CS Вызывает проверку линии "Очистка посылки" (Clear to send). За этой командой может следовать значение (от 0 до 65535), дающее число миллисекунд которые будет ожи- даться сигнал перед тем как будет выдана ошибка таймау- та, например, CS500. Значение по умолчанию 1000, если указан параметр RS, в этом случае 0. DS Вызывает проверку линии "Готовность набора данных" (Data set ready). Допускается необязательный параметр, как и для CS. Значение по умолчанию 1000. CD Вызывает проверку линии "Определение носителя" (Carrier detect). Допускается необязательный временной параметр, как и для CS. Значение по умолчанию 0. LF Вызывает автоматическую подачу кода перевода строки (ASCII 10) после каждого символа возврата каретки (AS- CII 13). Используется для последовательного вывода на принтер. PE Разрешает проверку четности, вызывая ошибку таймаута устройства при возникновении ошибки четности. Эти специальные команды могут помещаться в любом месте опера- тора OPEN "COM" и в любом порядке. Отметим, что обычно сигналы CTS и DSR должны быть установлены, чтобы оператор OPEN выполнил- ся, а иначе будет выдана ошибка таймаута устройства. В заключение приводим оператор OPEN "COM", содержащий все параметры, кроме RS и LF: OPEN "COM1:1200,O,7,1,CS2000,DS2000,CD,PE" AS #1 LEN = 256 Средний уровень. Функция 0 прерывания 14H BIOS инициализирует порт коммуника- ции. В DX должен даваться номер коммуникационного канала (COM1 = 0, COM2 = 1). В AL должен содержаться байт инициализационных данных, значение битов которого следующее: биты 1-0 длина слова. 10 = 7 битов, 11 = 8 битов. 2 число стоп-битов. 0 = 1, 1 = 2. 4-3 четность. 00 или 10 = нет, 01 = нечет., 11 = чет. 7-5 скорость обмена. 000 = 110 бод 001 = 150 бод 010 = 300 бод 011 = 600 бод 100 = 1200 бод 101 = 2400 бод 110 = 4800 бод 111 = 9600 бод В данном примере порт инициализируется со словом в 8 битов, одним стоп-битом и четной четностью. Скорость обмена 1200 бод. ;---присваиваем значения параметров переменным MOV WORDLENGTH,00000011B ;длина слова 8 битов MOV STOPBITS,00000000B ;1 стоп-бит MOV PARITY,00011000B ;четная четность MOV BAUDRATE,10000000B ;скорость 1200 бод ;---инициализируем COM1 MOV AL,0 ;чистим AL OR AL,WORDLENGTH ;устанавливаем нужные биты OR AL,STOPBITS ; OR AL,PARITY ; OR AL,BAUDRATE ; MOV AH,0 ;функция инициализации порта MOV DX,0 ;выбираем COM1 INT 14H ;инициализируем порт Низкий уровень. Независимо от того, занимаемся ли мы вводом или выводом, как минимум 4 регистра микросхемы 8250 должны быть инициализированы для операций обмена. Это регистры делителя скорости обмена, ре- гистр контроля линии и регистр разрешения прерывания. Инициализация скорости обмена. Делитель скорости обмена это число, на которое надо разделить частоту системных часов (1190000 герц), чтобы получить желаемую скорость обмена. Например, для скорости обмена 1200 бод делитель скорости обмена должен быть равен 96, поскольку 1190000/96 приб- лиженно равно 1200. Чем больше делитель, тем меньше скорость обмена. Скорости обмена 300 и меньше требуют двухбайтного числа для делителя. Старший байт посылается в 3F9H (или 2F9H), а млад- ший в 3F8H (2F8H). В обоих случаях бит 7 регистра управления линии должен быть установлен в 1 перед засылкой значений; в про- тивном случае по этим двум адресам значения будут адресованы в другие регистры (см. [7.1.0]). Вот некоторые значения, требуемые для обычных скоростей обмена: Скорость обмена 3F9H 3F8H 110 04H 17H 300 01H 80H 600 00H C0H 1200 00H 60H 1800 00H 40H 2400 00H 30H 3600 00H 20H 4800 00H 18H 9600 00H 0CH Всегда устанавливайте регистры скорости обмена первыми, так как они единственные, которые требуют, чтобы был установлен бит 7 в регистре контроля линии. После этого надо изменить содержимое регистра контроля линии, сбрасывая 7-й бит, чтобы все остальные доступы к регистрам были правильными. Поскольку регистр контроля линии является регистром только для записи, то нет способа вер- нуть бит 7 обратно в 1 без одновременной установки всех остальных битов этого регистра. Отметим, что PCjr использует другие делите- ли, описание которых Вы можете найти в техническом руководстве. Инициализация регистра контроля линии. Значение битов регистра контроля линии, адрес порта которого равен 3FBH (или 2FBH), следующее: биты 1-0 Длина символа. 00 = 5 битов, 01 = 6 битов 10 = 7 битов, 11 = 8 битов 2 Число стоп-битов. 0 = 1, 1 = 1.5, если длина пяти, иначе 2. 3 Четность. 1 = генерируется бит четности, 0 = нет. 4 Тип четности. 0 = нечетная, 1 = четная 5 Фиксация четности. Заставляет бит четности всегда быть 0 или 1. 0 = отменена 1 = всегда 1, если бит 3 = 1 & бит 4 = 0 или 1 = всегда 0, если бит 3 = 1 & бит 4 = 1 или 1 = нет четности, если бит 3 = 0 6 Установка перерыва. Вызывает вывод строки нулей в качестве сигнала отдаленной станции. 0 = запрещено, 1 = перерыв 7 Меняет адреса портов других регистров Обычно биты 5-7 сброшены в 0. Остальные описывают значения, опре- деляемые протоколом обмена. Регистр разрешения прерывания. Даже если Вы не используете прерывания, все равно Вы должны произвести запись в регистр разрешения прерывания, чтобы быть уверенным, что прерывания запрещены. Просто поместите в этот регистр 0. Регистр идентификации прерывания можно игнорировать. Инициализация остальных регистров связана с модемами. Ясно, что модемы нужны только для связи с удаленными устройствами, а не для управления близлежащими устройствами, такими как последова- тельный принтер. В [7.1.5] объяснено как инициализировать регистр контроля модема. В данном примере из области данных BIOS берется базовый адрес COM1, после чего различные регистры инициализируются для скорости обмена 1200 бод, семибитных данных, четной четности и одного стоп-бита. ;---получаем базовый адрес COM1 MOV AX,40H ;ES указывает на область данных BIOS MOV ES,AX ; MOV DX,ES:[0] ;получаем базовый адрес COM1 ;---инициализируеи регистры делителя скорости обмена на 1200 бод ADD DX,3 ;указываем на регистр контроля линии MOV AL,10000000B ;устанавливаем бит 7 OUT DX,AL ;посылаем байт DEC DX ;указываем на старший байт делителя DEC DX ;скорости обмена MOV AL,0 ;старший байт для 1200 бод OUT DX,AL ;посылаем старший байт для 1200 бод DEC DX ;указываем на младший байт делителя MOV AL,60H ;младший байт делителя для 1200 бод OUT DX,AL ;посылаем младший байт ;---инициализируем регистр контроля линии MOV AL,0 ;обнуляем AL OR AL,10B ;длина данных 7 битов OR AL,000B ;1 стоп-бит OR AL,1000B ;генерируется бит четности OR AL,10000B ;четная четность ADD DX,3 ;указывает на регистр контроля линии OUT DX,AL ;посылаем инициализационное значение ;---инициализируем регистр разрешения прерывания DEC DX ;указываем на регистр разрешения DEC DX ;прерывания MOV AL,0 ;запрещаем прерывания OUT DX,AL ;посылаем байт 7.1.3 Установка текущего коммуникационного порта. Имеются два способа, которыми программа может определить, какой из коммуникационных портов должен использоваться. Один из способов состоит в указании номера канала в операторе программы. Второй способ состоит в написании программы для обмена через порт COM1, но изменении коммуникационного адаптера, доступ к которому идет через COM1. Область данных BIOS содержит место для четырех 2-хбайтных переменных, которые содержат базовые адреса коммуникационных каналов (MS DOS поддерживает только первые два из них). Базовый адрес порта это младший из группы адресов портов, через которые можно получить доступ к данному коммуникационному каналу. Базовый адрес для COM1 хранится в ячейке 0040:0000, а для COM2 - в ячейке 0040:0002. Для смены коммуникационных портов надо просто поменять эти два значения. Повторная смена значений приведет к первона- чальному назначению портов. Высокий уровень. В Бейсике оператор OPEN "COM" может использоваться в виде OPEN C$+"1200,N,8" AS #2, где C$ может быть либо "COM1:", либо "COM2:". В качестве альтернативы можно использовать PEEK и POKE для обмена базовых адресов: 100 DEF SEG = &H40 'указываем на область данных BIOS 110 X = PEEK(0): Y = PEEK(1) 'запоминаем первые 2 байта 120 POKE 0,PEEK(2): POKE 1,PEEK(3) 'переносим 2-е два байта 130 POKE 2,X: POKE 3,Y 'засылаем запомненные значения Средний уровень. Если программа обращается к коммуникационному порту через прерывание 14H BIOS, то COM порт определяется содержимым DX, которое равно 0 или 1 (для COM1 или COM2). Вместо того, чтобы присваивать DX непосредственное значение, заполняйте его из пере- менной, которой может быть присвоено значение 0 или 1. Программы, использующие коммуникационные функции 3 и 4 прерывания 21H всегда адресуются к COM1. В этом случае надо поменять базовые адреса: ;---обмен базовых адресов для COM1 и COM2 MOV AX,40H ;ES указывает на область данных BIOS MOV ES,AX ; MOV DX,ES:[0] ;помещаем 1-й базовый адрес в DX MOV AX,ES:[2] ;помещаем 2-й базовый адрес в AX MOV ES:[0],AX ;обмениваем адреса MOV ES:[2],DX ; 7.1.4 Определение статуса коммуникационного порта. Регистр статуса линии микросхемы UART 8250 определяет протокол связи. Этот регистр имеет адрес порта на 5 больше, чем базовый адрес данного канала. Обычно он постоянно просматривается в про- цессе коммуникационного обмена. При передаче данных регистр сооб- щает, что предыдущий символ уже послан, позволяя программе запи- сать новый символ поверх его. При приеме данных регистр информи- рует программу о поступлении следующего символа, с тем чтобы программа могла прочитать его прежде чем он будет уничтожен сле- дующим прибывшим. Значение битов этого регистра следующее: бит 0 1 = байт данных получен 1 1 = полученные данные были перезаписаны (предыдущий символ не был вовремя считан) 2 1 = ошибка четности (вероятно, из-за шума в линии) 3 1 = ошибка окружения (передача не синхронизована) 4 1 = обнаружен перерыв (получена длинная строка единиц, индицирующая, что другая станция запрашивает конец передачи) 5 1 = регистр хранения передатчика пуст (в этот регистр должны помещаться передаваемые данные) 6 1 = регистр сдвига передатчика пуст (этот регистр по- лучает данные из регистра хранения и преобразует их в последовательный вид) 7 1 = таймаут (устройство не связано с машиной) Высокий уровень. В Бейсике сначала определите базовый адрес используемого ком- муникационного порта, затем добавьте к нему 5 и используйте опе- ратор INP для получения байта из этого порта. В приложении Б объясняется как в Бейсике производятся битовые операции, которые необходимо проделать программе, чтобы интерпретировать значение этого байта. В следующем примере проверяется бит наличия переры- ва: 100 DEF SEG = &H40 'указываем на область данных BIOS 110 ADDR = PEEK(4)+PEEK(5)*256 'вычисляем адрес COM2 120 X = INP(ADDR+5) 'вычисляем адрес регистра статуса 130 IF X AND 16 THEN 500 'переход на подпр-му, если бит 4 = 1 . . 500 '''начинаем процедуру обработки перерыва Средний уровень. Функция 3 прерывания 14H BIOS возвращает в AH регистр статуса линии (AL получает регистр статуса модема [7.1.5]). При входе DX должен содержать номер коммуникационного порта, к которому осу- ществляется доступ, где COM1 = 0, а COM2 = 1. Как и предыдущий пример, этот проверяет наличие перерыва: MOV AH,3 ;номер функции MOV DX,1 ;выбираем COM2 INT 14H ;получаем байт статуса TEST AH,10000B ;обнаружен перерыв? JNZ BREAK_DETECT ;если да, то на процедуру обработки Низкий уровень. Этот пример совершенно аналогичен приведенному на Бейсике. Из области данных BIOS считывается базовый адрес коммуникационного канала, к нему добавляется 5, а затем из полученного адреса порта считывается байт статуса. MOV AX,40H ;ES указывает на область данных BIOS MOV ES,AX ; MOV DX,ES:[2] ;получаем базовый адрес COM2 ADD DX,5 ;добавляем 5 для регистра статуса IN AL,DX ;получаем байт статуса TEST AL,10000B ;бит 5 установлен? JNZ BREAK_DETECT ;если да, то на обработку перерыва 7.1.5 Инициализация и управление модемом. Имеется 6 линий, по которым модемы связываются с компьютером (усовершенствованные модели могут иметь добавочные линии по ин- терфейсу RS232). Вот их названия, сокращения и функции: От компьютера к модему: Data Terminal Ready (DTR) Информирует модем, что компьютер Готовность компьютера включен и готов к связи. Request To Send (RTS) Информирует модем, что компьютер Запрос на посылку ожидает посылки данных. От модема к компьютеру: Data Set Ready (DSR) Информирует компьютер, что модем Готовность модема включен и готов. Clear To Send (CTS) Информирует компьютер, что модем Готовность к посылке готов начать передачу данных. Data Carrier Detect (DCD) Информирует компьютер, что модем Обнаружен носитель данных связан с другим модемом. Ring Indicator (RI) Информирует компьютер, что теле- Индикатор звонка фонная линия, по которой присое- динен модем имеет звонок. Сначала компьютер устанавливает сигнал DTR, а затем инстукти- рует модем связаться с удаленной станцией. После того, как модем установил связь он устанавливает сигнал DSR. Этот сигнал информи- рует компьютер, что модем готов к связи и в этот момент компьютер может установить сигнал RTS. Когда модем ответит сигналом CTS, то передача начинается. Две стандартные линии, по которым компьютер управляет модемом, доступны через регистр контроля модема микросхемы UART 8250. Этот регистр имеет адрес порта на 4 больше, чем базовый адрес исполь- зуемого коммуникационного канала. Вот значение его битов: Регистр контроля модема: биты 7-5 (всегда 0) 4 1 = выход UART замкнут на вход 3 добавочный пользователь назначен на вывод #2 2 добавочный пользователь назначен на вывод #1 1 1 = "запрос на посылку" активен 0 1 = "готовность компьютера" активна Обычно установлены биты 0 и 1 регистра контроля модема, а остальные равны 0. Бит 2 равен 0, за исключением случаев, когда производитель модема предназначил его для специального использо- вания. Бит 3 установлен только в случае, когда используются пре- рывания [7.1.8]. Наконец, бит 4 предоставляет возможность тести- рования коммуникационных программ без установления реальной свя- зи. Выходной сигнал микросхемы UART подается на вход, как будто UART принимает последовательные данные. Это свойство можно ис- пользовать для тестирования правильности работы самой микросхемы. Оно недоступно при использовании коммуникационных процедур преры- вания 14H BIOS. Четыре линии, по которым модем посылают информацию компьютеру, управляются регистром статуса модема. Этот регистр расположен по адресу порта на 6 больше, чем базовый адрес используемого комму- никационного адаптера. Вот значение его битов: Регистр статуса модема: бит 7 1 = DCD 6 1 = RI 5 1 = DSR 4 1 = CTS 3 1 = изменение в DCD 2 1 = изменение в RI 1 1 = изменение в DSR 0 1 = изменение в CTS Программа непрерывно проверяет эти биты в ходе коммуникацион- ных операций. Отметим, что 4 младших бита параллельны старшим четырем битам. Эти биты устанавливаются в 1 только тогда, когда происходит изменение в статусе соответствующего старшего бита с тех пор, когда регистр читался последний раз. Все 4 младших бита автоматически сбрасываются при чтении регистра. Программы любого уровня могут прямо читать этот регистр. Другой возможностью яв- ляется использование функции 3 прерывания 14H BIOS, которая возв- ращает регистр статуса модема в AL (при этом в AH будет содер- жаться регистр статуса линии). При входе DX должен содержать номер коммуникационного канала (0 или 1). Большинство модемов имеет намного больше возможностей, по сравнению с теми, что отражены в двух связанных с модемом регист- рах. Имеются возможности автоматической связи и автоматического ответа, которые контролируются управляющей строкой. Эта строка посылается в модем, как будто передаются обычные данные. Модем выделяет эту строку из данных по специальному символу, используе- мому только для указания начала управляющей строки. Этот символ может быть предопределенным (часто используется код Esc - ASCII 27) или выбираемым пользователем. Модем способен определить нас- колько длинной должна быть каждая строка, поэтому по окончании строки он опять рассматривает входящий поток информации как дан- ные. Каждый модем имеет свой набор команд. В качестве примера рассмотрим команды, используемые внутренним модемом PCjr: Символ Значение Применение A ответ вход в режим ответа Bn перерыв посылает сигнал перерыва n*100 мс Cn отсчет n отсчитывает n звонков до ответа Dn...n вызов посылает строку чисел n...n Fn формат устанавливает протокол связи H разрыв прекращает связь с машиной I инициализация инициализирует модем LR долгий ответ меняет используемую кодовую систему M режим модем берет символы как данные Nn новый меняет командный символ на n O originate вход в режим originate P pick-up вход в режим голоса Q запрос запрос статуса модема R повтор повторить команду связи Sn скорость выбор скорости обмена Tn...n прозрачность игнорировать управляющие строки в следующих n...n байтах V голос перевести модем в режим голоса W ожидание ничего не делать до след. команды X передать передать тона вызова Z тест проводит диагностику оборудования В ответ на команду запроса модем посылает информацию о своем состоянии, посылая ее в UART как обычные данные. Помимо прочей информации, может сообщаться, что линия занята. Чтобы правильно использовать команды управления модемом и информацию о его стату- се надо тщательно изучить документацию на данный модем. Модем PCjr описан в техническом руководстве по PCjr. Нижеприведенные примеры дают только голую схему установления связи через модем. Высокий уровень. Поскольку телефонная связь очень медленная, то связь с модемом это одна из областей, где программирование связи на Бейсике ничем не хуже, чем на языке ассемблера. Вот грубая схема: 100 OUT BASEADDRESS+4,1 'устанавливаем бит DTR 110 '''теперь посылаем управляющую строку для вызова и установле- 120 '''ния связи - этот код меняется от модема к модему . . 200 X = INP(BASEADDRESS+2) 'получаем регистр статуса модема 210 IF X AND 2 <> 2 THEN 200 'ждем пока будет установлен бит 1 220 OUT BASEADDRESS+4,3 'устанавливаем бит RTS 230 X = INP(BASEADDRESS+2) 'получаем регистр статуса модема 240 IF X AND 1 <> 1 THEN 230 'ждем пока будет установлен бит 0 250 '''теперь посылаем данные Низкий уровень. Вот та же самая схема на языке ассемблера: ;---устанавливаем сигнал DTR MOV DX,BASE_ADDRESS ;начинаем с базового адреса ADD DX,4 ;указываем на регистр контроля модема MOV AL,1 ;устанавливаем бит 1 OUT DX,AL ;посылаем в порт ;---посылаем управляющую строку модему для вызова . (этот код разный для разных модемов) . ;---ожидаем пока будет установлен сигнал DSR INC DX ;указываем на регистр статуса модема INC DX ; TRY_AGAIN: IN AL,DX ;получаем содержимое TEST AL,10B ;проверяем второй бит JZ TRY_AGAIN ;ждем пока он не будет равен 1 ;---устанавливаем бит RTS DEC DX ;возвращаемся к регистру управления DEC DX ; MOV AL,3 ;устанавливаем сигнал RTS OUT DX,AL ;посылаем в порт ;---ожидаем сигнала CTS INC DX ;возвращаемся к регистру статуса INC DX ; ONCE_MORE: IN AL,DX ;получаем байт статуса TEST AL,1 ;проверяем бит CTS JZ ONCE_MORE ;не продолжаем пока он не установлен ;---теперь можно посылать данные 7.1.6 Передача данных. Передача данных проще чем прием, поскольку программа имеет полный контроль над составом данных и скоростью, с которой они должны посылаться. Тем не менее процедуры передачи могут быть достаточно сложными, если они обрабатывают данные по мере того, как они посылаются. Могут быть также проблемы с синхронизацией при использовании протокола XON/XOFF. Этот протокол использует коды ASCII 17(XON) и 19(XOFF), для того чтобы сигнализировать принимающей станции, что передатчик хочет продолжить передачу временно прерванного потока данных. Чтобы принять эти сигналы, программа должна непрерывно анализировать принимаемые символы при передаче (в полнодуплексном режиме, в котором обычно работают модемы, сигналы одновременно идут в обе стороны по телефонному каналу). Кроме того, чтобы обнаружить, что удаленная станция посылает строку нулей, в качестве сигнала перерыва, должен непре- рывно анализироваться статус бита перерыва (номер 4) регистра статуса линии [7.1.4]. На рис. 7-2 (в [7.1.7]) показано как про- цедура передачи данных взаимодействует с кодом, принимающим дан- ные. Вследствие этих причин, представленные в этом пункте процедуры отдельно передающие данные являются искуственными. Но их можно скомбинировать с процедурами приема данных, описанными в [7.1.7] для создания общего представления о том, что нужно. Ясно, что для создания работоспособной процедуры необходимо затратить большие усилия, особенно в части обнаружения и исправления ошибок при передаче данных. Высокий уровень. В Бейсике для того, чтобы послать данные в открытый коммуника- ционный порт надо использовать операторы PRINT#, PRINT# USING или WRITE#. Последние два оператора имеют специальный формат, парал- лельный тому, который используется ими при выводе на дисплей. Обычно используется оператор PRINT#. В данном примере посылаемые данные берутся непосредственно с клавиатуры. Предполагается, что COM1 уже открыт, как показано в [7.1.2]. Процедура обрабатывает бит перерыва в регистре статуса линии. . . 500 C$ = INKEY$: IF C$ <> "" THEN PRINT #1,C$ 510 X = INP(BASEADDRESS + 5) 'читаем регистр статуса линии 520 IF X AND 32 = 32 THEN 1000 'проверяем бит перерыва 530 IF EOF(1) THEN 500 'если буфер пуст, то ждем ввода . (здесь расположена процедура приема данных) . 1000 '''здесь процедура обработки перерыва Средний уровень. Функция 1 прерывания 14H BIOS посылает символ, содержащийся в AL в коммуникационный канал. При входе DX содержит номер порта (0 или 1). При возврате AH содержит байт статуса, в котором бит 7 = 1, если операция неуспешна. В этом случае имеют значение следую- щие биты: бит 4 обнаружен перерыв (сигнал "стоп" от принимающей станции) 5 регистр сдвига передатчика пуст 6 регистр хранения передатчика пуст MS DOS имеет функцию для передачи по коммуникационному каналу символа, помещаемого в DL. Это функция номер 4 прерывания 21H, но она не имеет никаких преимуществ перед функцией BIOS; она не возвращает статусной информации и не позволяет назначать какой из коммуникационных портов надо использовать (всегда используется COM1). Чтобы вывести строку данныз используйте функцию 40H прерывания 21H. Это обычная функция вывода для всех файлов и устройств при использовании метода доступа дескриптора файлов. COM1 имеет пре- лопределенный номер #3. Поместите номер файла в BX, а число пере- даваемых байтов в CX. Пусть DS:DX указывают на буфер выводимых данных и вызывайте функцию. MOV AH,40H ;номер функции MOV BX,3 ;предопределенный номер файла для COM1 MOV CX,50 ;выводим 50 байтов LEA DX,DATA_BUFFER ;DS:DX указывают на буфер данных INT 21H ;посылаем данные JC COM_ERROR ;уход на обработку ошибки Отметим, что при использовании предопределенных номеров файлов их не надо открывать. Если произошла ошибка, то устанавливается флаг переноса, а в AX возвращается 5 если коммуникационный порт не готов и 6 при указании неверного номера файла. Низкий уровень. Когда байт данных помещается в регистр хранения передатчика, то он автоматически выводится в последовательный канал через регистр сдвига передатчика, который сериализует данные. Нет необ- ходимости в импульсе бита строба, как это делается в случае па- раллельного адаптера. Бит 5 регистра статуса линии показывает свободен ли регистр хранения передатчика для приема данных. Ре- гистр постоянно проверяется до тех пор, пока бит 5 не станет равным 1. После этого в регистр хранения передатчика посылается очередной байт из того места, откуда они берутся. В процессе передачи бит 5 равен 0 и только когда он опять станет равным 1, то в регистр хранения передатчика может быть послан следующий символ. Этот процесс повторяется до тех пор, пока это нужно. В следующем примере даны основные понятия об этой процедуре. Конечно, она может быть сделана необычайно сложной (в частности, программирование связи требует особо тщательных процедур обнару- жения ошибок и восстановления при сбоях). В примере предполагает- ся, что коммуникационный порт и модем уже инициализированы, как показано в [7.1.2] и [7.1.5]. Первая часть это цикл проверки ошибок и приема символов. В [7.1.7] приведен код для процедуры приема данных. ;---ждем пока все будет готово для посылки символа KEEP_TRYING: MOV DX,BASE_ADDRESS ;базовый адрес ADD DX,5 ;указываем на регистр статуса линии IN AL,DX ;получаем байт статуса TEST AL,00011110B ;проверяем на ошибку JNZ ERROR_ROUTINE ;если есть, то на процедуру обработки TEST AL,00000001B ;проверяем получены ли данные JNZ RECEIVE ;если да, то на процедуру приема TEST AL,00100000B ;проверяем готовность к передаче JZ KEEP_TRYING ;если нет, то возвращаемся назад ;---передаем символ принимаемый с клавиатуры MOV AH,1 ;функция проверки нажатия клавиши INT 16H ;прерывание клавиатуры BIOS JZ KEEP_TRYING ;возврат, если не было нажатия MOV AH,0 ;функция получения кода с клавиатуры INT 16H ;теперь нужный символ в AL SUB DX,5 ;адрес регистра хранения передатчика OUT DX,AL ;посылаем символ JMP SHORT KEEP_TRYING ;возвращаемся к началу цикла 7.1.7 Получение данных. Коммуникационная программа готова принимать данные как только инициализирован коммуникационный порт [7.1.2] и установлена связь с удаленной станцией [7.1.5]. Прием данных никогда полностью не отделен от передачи данных, поскольку программе может потребо- ваться послать сигнал XOFF (ASCII 19), чтобы остановить поток данных, если они поступают слишком быстро и она не успевает их обрабатывать. Код XON (ASCII 17) сообщает удаленной станции, что можно продолжить передачу. Отметим, что PCjr не может принимать данные во время дисковых операций; чтобы снять это ограничение можно использовать XON и XOFF. В зависимости от сложности используемого протокола обмена, принимаемые данные могут требовать простой или сложной обработки. Может быть получен один из набора управляющих кодов, приведенных в [7.1.9]. Те из них, которые являются ограничителями данных чаще обнаруживаются при синхронном обмене. При выводе получаемых сим- волов на экран учитывайте влияние символов перевода строки (ASCII 10), поскольку некоторые языки (включая Бейсик) автоматически вставляют перевод строки после возврата каретки; в этом случае исключайте переводы строки из принимаемых данных, чтобы избежать пустых строк при выводе. На рис. 7-2 показана коммуникационная процедура, включающая также код передачи, обсуждаемый в [7.1.6]. Высокий уровень. Для коммуникационной процедуры, написанной на интерпретируемом Бейсике, время очень существенно. Обработка медленна, поэтому если процедура приема неверно сконструирована, то входной буфер может заполниться (т.е. произойдет переполнение) в то время как программа еще будет анализировать ранее полученные данные. Оче- видным решением этой проблемы является максимально возможный размер буфера. При загрузке Бейсика размер буфера ввода устанав- ливается добавлением к команде ключа /C:. BASICA /C:1024 создает буфер размером в 1K и это минимальное число для скорости обмена 1200 бод (сложным процедурам может понадобиться 4096 байт). По умолчанию используется размер буфера равный 256 байтам и такой буфер имеет то преимущество, что он может быть целиком помещен в одну символьную переменную. Такой размер буфера можно использо- вать только при скорости обмена 300 бод и ниже. Бейсик читает из буфера с помощью оператора INPUT$ (можно использовать также INPUT# и LINE INPUT#, но INPUT$ более гибок). Этот оператор имеет форму INPUT$(числобайт,номерфайла). Например, INPUT$(10,#1) читает 10 байтов из коммуникационного канала, отк- рытого как файл #1. Если размер буфера не превышает 256 байтов, то очень удобно читать все содержимое буфера за один раз. LOC сообщает сколько байтов данных находится в буфере в данный мо- мент. Поэтому напишите оператор INPUT$(LOC(1),#1) и в S$ будут записаны все данные с момента последнего доступа к буферу. Конеч- но, если LOC(1) = 0, то буфер пуст и процедура должна ожидать пока данные будут получены. Отметим, что EOF(1) также можно ис- пользовать для проверки состояния буфера, так как эта функция возвращает -1 если буфер пуст и 0, если там есть хотя бы один символ. После того как данные записаны в S$ программа должна проверить не содержатся ли там управляющие коды. Функция INSTR выполняет эту задачу быстрее всего. Напомним, что ее параметрами являются сначала позиция, с которой надо вести поиск в строке, затем имя строки и, наконец, символ (или строка) который ищется. Чтобы найти символ XOFF (ASCII 19) оператор должен иметь вид INSTR(1,S$,CHR$(19)). Чтобы найти второе появление нужного управ- ляющего символа повторите поиск в строке, начиная с символа, следующего за позицией, в которой найден первый. Обычно процедура ввода исключает большинство управляющих сим- волов из принимаемых данных, с тем чтобы они нормально выглядели при выводе. Затем данные выводятся на экран, пересылаются в дру- гое место в памяти, а иногда записываются на диск или выводятся на принтер. В процессе всей этой деятельности программа должна постоянно возвращаться к просмотру не поступили ли новые данные. Если оказалось, что буфер заполняется слишком быстро, то програм- ма может послать сигнал XOFF, останавливая поток данных. Затем, после того как полученные данные буду декодированы, можно снова разрешить передачу данных. Конечно, необходимо чтобы протокол обмена поддерживал XON и XOFF. Программы, написанные на интерпре- тируемом Бейсике, обычно могут использовать XON/XOFF для установ- ления соответствия скоростей при приеме данных, но при передаче данных такая программа часто не может достаточно быстро отреаги- ровать на получение сигнала XOFF. . . 500 '''здесь находится процедура передачи (см. [7.1.6]) . . 600 IF LOC(1)>100 THEN XOFF = 1: PRINT #1,CHR$(19) 610 C$ = INPUT$(LOC(1),#1) 'читаем содержимое буфера 620 '''выделяем из данных управляющие символы 630 IF INSTR(1,C$,CHR$(19))>0 THEN 800 'получен XOFF 640 IF INSTR(1,C$,CHR$(17))>0 THEN 900 'получен XON . (здесь удаляются ненужные управляющие символы . 700 PRINT C$ 'выводим данные на экран 710 IF LOC(1) > 0 THEN 600 'если получены данные, то читаем их 720 IF XOFF = 1 THEN XOFF = 0: PRINT #1,CHR$(17) . . 800 'реакция на XOFF . 900 'реакция на XON Если функция LOF применяется к коммуникационному порту, то она возвращает количество свободного места, оставшееся в буфере вво- да. Например, если COM1 открыт как #1, то LOF(1) сообщит свобод- ного пространства. Это может быть полезно для определения, что буфер почти полон. Отметим, однако, что оператор LOC возвращает позицию указателя в буфере и это значение может быть использовано для той же цели. Например, если COM1 открыт как #3, а размер буфера ввода равен 256 байтам, то до тех пор, пока LOC(3) не будет равен 256, буфер не полон. Средний уровень. Функция 2 прерывания 14H BIOS ожидает символ из последователь- ного порта, помещает его в AL при получении и затем возвращается в программу. При входе надо поместить номер порта (0-1) в DX. При возврате AX равен нулю, если не было ошибки. Если AH не равен 0, то может быть возвращен байт статуса, в котором имеют значение только 5 битов. Это следующие биты: бит 1 ошибка переполнения (новый символ поступил раньше, чем был удален старый) 2 ошибка четности (вероятно, из-за проблем в линии) 3 ошибка оформления (стартовый или стоп-биты неверны) 4 обнаружен перерыв (получена длинная строка битов 0) 5 ошибка таймаута (не получен сигнал DSR) MS DOS также предоставляет коммуникационную функцию для приема одного символа, это функция 3 прерывания 21H. Функция ожидает символ из COM1 и помещает его в AL. Отметим, что при этом нет функции инициализации порта, которую надо делать через процедуру BIOS или непосредственно, как показано в [7.1.2]. По умолчанию порт инициализируется со значениями 2400 бод, нет контроля чет- ности, один стоп-бит и 8 битов на символ. Эта функция не имеет никаких достоинств по сравнению с функцией BIOS и не возвращает информации о статусе. Низкий уровень. При получении данных без использования коммуникационного пре- рывания [7.1.8] программа должна постоянно проверять регистр статуса линии, адрес порта которого на 5 больше базового адреса используемого коммуникационного адаптера. Бит 0 этого регистра будет равен нулю, до тех пор пока не будет получен символ в ре- гистр данных приемника. Когда бит 0 становится равным 1, то надо немедленно считать его из регистра, с тем чтобы на него не нало- жился следующий принимаемый символ. После того как символ считан, бит 0 опять становится равным 0 и остается таковым, пока не при- будет новый символ. Хотя здесь об этом не говорилось, но коммуникационные процеду- ры обычно создают циклический буфер для сбора поступающих симво- лов. Циклические буфера обсуждались в [3.1.1]. Вы должны также знать, что если поступающие данные подавать на экран со скоростью 1200 бод, то процедура сдвига экрана BIOS [4.5.1] не будет успе- вать и произойдет переполнение. Простое решение этих проблем состоит в использовании коммуникационного прерывания, как объяс- нено в [7.1.8]. Следующий пример частично дублирует содержимое предыдущего раздела, относящегося к передаче символов. Как и в том случае код начинается с бесконечного цикла. Объедините эти 2 процедуры с процедурами инициализации из [7.1.2] и [7.1.5] для создания за- конченной процедуры ввода/вывода через коммуникационный канал. KEEP_TRYING: MOV DX,BASE_ADDRESS ;базовый адрес ADD DX,5 ;указываем на регистр статуса линии IN AL,DX ;получаем байт статуса TEST AL,00011110B ;проверяем на ошибку JNZ ERROR_ROUTINE ;если да, то на обработку ошибки TEST AL,00000001B ;проверяем получены ли данные JNZ RECEIVE ;на процедуру приема данных TEST AL,00100000B ;проверяем готовность к передаче JZ KEEP_TRYING ;если нет, то к началу цикла . (здесь расположена процедура передачи - см. [7.1.6]) . ;---получаем данные и выводим их на экран RECEIVE: MOV DX,BASE_ADDRESS ;базовый адрес IN AL,DX ;читаем полученный символ CMP AL,19 ;проверка на XOFF JE XOFF_ROUTINE ; . (и т.д.) . MOV DL,AL ;готовим символ для вывода на экран MOV AH,2 ;функция вывода символа INT 21H ;выводим его JMP SHORT KEEP_TRYING ;возвращаемся на начало цикла 7.1.8 Посылка/получение данных с помощью коммуникационного прерывания. Хорошая коммуникационная программа имеет слишком много работы, чтобы посвятить себя целиком вводу/выводу. Поступающие данные должны анализироваться, передаваемые данные должны собираться, а большие блоки данных могут записываться на диск или считываться с него. Коммуникационное прерывание позволяет программе не тратить на ввод/вывод больше времени, чем он того требует. Например, после установки прерывания, управление передается процедуре пере- дачи данныз только в том случае, когда регистр хранения передат- чика пуст и возвращается программе, как только послан байт дан- ных, позволяя ей продолжать свою работу до тех пор, пока регистр хранения передатчика не будет снова готов. Не забудьте ознако- миться с обсуждением прерываний в [1.2.3], прежде чем продолжить чтение. IBM PC отводит два аппаратных прерывания для коммуникационных каналов, номер 3 (COM1) и 4 (COM2). Отметим, что у PCjr, встроен- ный модем имеет номер 3, а COM1 - номер 4. Микросхема UART 8250 допускает 4 класса прерываний для каждого канала, используя сле- дующие двоичные кодовые числа: 00 изменение в регистре статуса модема 01 регистр хранения передатчика пуст 10 получены данные 11 ошибка приема, или получено условие перерыва Эти коды содержатся в битах 2-1 регистра идентификации прерыва- ния, адрес порта которого на 2 больше, чем базовый адрес исполь- зуемого коммуникационного адаптера. Бит 0 этого регистра устанав- ливается при возникновении прерывания, а остальные биты не ис- пользуются и всегда равны 0. Чтобы выбрать одно или более прерываний, надо запрограммиро- вать регистр разрешения прерывания, адрес которого на 1 больше базового адреса. Значение его битов такое: бит 0 1 = прерывание при получении данных 1 1 = прерывание когда регистр хранения передатчика пуст 2 1 = прерывание при ошибке приема данных 3 1 = прерывание при изменении регистра статуса модема 7-4 не используются, всегда 0 Когда одно из этих событий происходит, то инициируется аппаратное прерывание, возникающее в микросхеме обработки прерываний 8259 по каналу 3 для COM1 и по каналу 4 для COM2. Процедура обработки прерываний передает управление тому коду, на который указывает соответствующий вектор прерывания. Поскольку это аппаратное пре- рывание, то оно может быть маскировано [1.2.2]. Помните, что процедура обработки прерывания должна завершаться стандартным кодом выхода из аппаратного прерывания MOV AL,20H/OUT 20H,AL. На рис. 7-3 показано коммуникационное прерывание. Любое число типов прерывания может быть разрешено одновремен- но. Но если разрешен более чем один тип, то процедура обработки прерывания должна сама определять какой из типов прерывания прои- зошел, проверяя регистр идентификации прерывания. Одновременно могут происходить более чем одно прерывание, поэтому бит 0 ре- гистра идентификации сообщает о том, что поступило еще одно пре- рывание. Когда два или более прерываний поступило в один и тот же момент времени, то они обрабатываются в порядке, указанном в нижеприведенной таблице. Добавочные прерывания должны быть обра- ботаны до завершения процедуры обработки прерывания. Условия предшествующих прерываний "отменяются" с помощью действий, при- веденных в правом столбце следующей таблицы: Код Тип Действия для "сброса" 11 ошибка или перерыв чтение регистра статуса линии 10 получены данные чтение регистра приемника данных 01 передатчик готов вывод символа в регистр хранения передатчика 00 изменение статуса модема чтение регистра статуса модема Низкий уровень. Вот общая форма программы, обрабатывающей коммуникационные прерывания: ;---установка вектора коммуникационного прерывания PUSH DS ;сохраняем DS MOV DX,OFFSET IO_INT ;DS:DX указывают на процедуру MOV AX,SEG IO_INT ; MOV DS,AX ; MOV AL,0BH ;номер вектора для COM1 MOV AH,25H ;функция изменения вектора INT 21H ;меняем вектор прерывания ;---инициализация регистра разрешения прерывания (COM1) MOV AX,40H ;DS указывает на данные BIOS MOV DS,AX ; MOV DX,DS:[0] ;получаем базовый адрес COM1 INC DX ;указываем на регистр разрешения MOV AL,3 ;прерываний и разрешаем прерывания OUT DX,AL ;приема и передачи POP DS ;восстанавливаем регистр ;---процедура обработки прерывания - сначала определяем его тип IO_INT PROC FAR NEXT_INT: MOV DX,BASEADDRESS ;базовый адрес INC DX ;указываем на регистр идентификации INC DX ;прерывания IN AL,DX ;читаем его значение TEST AL,10B ;это прерывание передатчика? JNZ TRANSMIT ;если да, то на передачу RECEIVE: ;иначе на прием . . JMP SHORT ANOTHER ;проверяем нет ли другого прерывания TRANSMIT: ;здесь код для передачи . . ;---перед выходом, проверяем нет ли другого прерывания ANOTHER: MOV DX,BASEADDRESS ;базовый адрес INC DX ;указываем на регистр идентификации INC DX ;прерывания IN AL,DX ;читаем его значение TEST AL,1 ;проверяем бит 1 JNZ NEXT_INT ;если он установлен, то на начало MOV AL,20H ;иначе код завершения аппаратного OUT 20H,AL ;прерывания IRET IO_INT ENDP 7.1.9 Сводка управляющих кодов, используемых при коммуникации. Эта таблица содержит 32 управляющих кода ASCII, которые ис- пользуются при коммуникации, а также при работе принтера и других устройств. Добавлен также код ASCII 127 - DEL (Забой), который обычно используется как управляющий код, хотя его и нельзя выдать с клавиатуры с помощью сочетания Ctrl + клавиша. Применение неко- торых кодов, таких как возврат каретки, инвариантно. Но боль- шинство других управляющих кодов имеют широкий диапазон интерпре- тации, во многом из-за отсутствия совместимости оборудования. Номер кода ASCII Комбинация Мнемо- 10-й 16-й Символ с Ctrl ника Назначение 00 00 ^@ NUL Символ-разделитель (не имеющий значения, поэтому полезен для задержек) 01 01 ^A SOH Начало заголовка. Начинает передачу блока данных или но- вого файла. 02 02 ^B STX Начало текста. Отмечает начало текста, следующего за за- головком данных. 03 03 ^C ETX Конец текста. Может отмечать начало данных, служащих для контроля ошибок. 04 04 ^D EOT Конец передачи. Код остановки, но иногда он просто отме- чает конец файла. 05 05 ^E ENQ Запрос. Запрашивает статусную информацию у отдаленной станции. 06 06 ^F ACK Подтверждение. Подтверждает успешный обмен между станциями. 07 07 ^G BEL Звонок. Инициирует звонок, чтобы привлечь внимание. 08 08 ^H BS Возврат на шаг. 09 09 ^I HT Горизонтальная табуляция. 10 0A ^J LF Перевод строки. 11 0B ^K VT Вертикальная табуляция. 12 0C ^L FF Перевод формата. 13 0D ^M CR Возврат каретки. 14 0E ^N SO Сдвиг выключен. Переключает набор символов. 15 0F ^O SI Сдвиг включен. Переключает набор символов. 16 10 ^P DLE Data Link Escape. Модифицирует значение следующих символов (аналогично Esc). 17 11 ^Q DC1 Управление устройством 1. Используется как сигнал XON для удаленной станции на передачу. 18 12 ^R DC2 Управление устройством 2. Сигнал переключения общего назна- чения. 19 13 ^S DC3 Управление устройством 3. Используется как сигнал XOFF для удаленной станции для прекращения передачи. 20 14 ^T DC4 Управление устройством 4. Сигнал переключения общего назна- чения. 21 15 ^U NAK Отрицание. Передача неуспешна. 22 16 ^V SYN Промежуток синхронизации. Используется между блоками данных при синхронной связи. 23 17 ^W TB Конец блока передачи. Вариант ETX. 24 18 ^X CAN Отмена. Обычно сигнализирует об ошибке передачи. 25 19 ^Y EM Конец среды. Сигнализирует о физическом конце источника данных. 26 1A ^Z SUB Подстановка. Заменяет символы, которые незаконны или невоз- можно вывести. 27 1B ^[ ESC Отмечает последующие символы, как управляющую последова- тельность. 28 1C ^/ FS Разделитель файлов. Отмечает логическую границу между фай- лами. 29 1D ^] GS Разделитель групп. Отмечает логическую границу между груп- пами данных. 30 1E ^^ RS Разделитель записей. Отмечает логическую границу между за- писями данных. 31 1F ^_ US Разделитель объектов. Отмечает логическую границу между объектами данных. 127 7F нет DEL Забой. Удаляет другие символы. Раздел 2. Создание драйвера устройства. Драйвер устройства это специальная программа, которая управ- ляет обменом с периферийным устройством, таким как принтер или дисковый накопитель. Поскольку параметры этих периферийных уст- ройств меняются от производителя к производителю, то разным поль- зователям программы может потребоваться дюжина различных драйве- ров, чтобы он мог работать на имеющемся у него оборудовании. Имеется 4 способа включения драйверов устройств в программу: 1. Можно поместить код для всех драйверов прямо в программу. Например, чтобы поддерживать различные принтеры, можно создать таблицу управляющих последовательностей и искать в ней нужный код каждый раз когда он потребуется. Этот подход тратит много памяти и может быть достаточно медленным. 2. Создать ряд драйверов устройств и потребовать, чтобы прог- рамма загружала необходимый в качестве оверлея (т.е. помещать его в область программы, специально оставленную для этой цели [1.3.5]). 3. Создать драйвер устройства как отдельную программу, которая указывается в командном файле, выполняемом при загрузке системы. Программа запускается и устанавливает драйвер устройства как программу обработки прерывания. После этого программа завершает- ся, но остается резидентной в памяти, как объяснено в [1.3.4]. Впоследствии наша программа использует этот драйвер через вектор прерывания. 4. Создать полноценный драйвер устройства, который будет заг- ружаться при старте с помощью файла CONFIG.SYS. MS DOS поддержи- вает такой тип драйверов устройств и однажды загруженный он может использовать все возможности команд DOS, включая проверку ошибок. Специальная команда IOCTL (Контроль ввода/вывода) позволяет прог- рамме узнать статус драйвера и послать ему управляющую строку, помимо обычного потока данных. Первые три стратегии легко реализуются с помощью информации, приведенной в остальных частях данной книги. Но устанавливаемые драйверы устройств очень сложны. Зато когда он есть, то он очень мощен. В этом случае система будет работать с устройством нас- только же тесно, как с клавиатурой или дисковым накопителем. Устройству может быть присвоено имя, например, SERIALPR для пос- ледовательного принтера, и затем это устройство может быть откры- то для доступа из любого языка. В Бейсике оператор OPEN "SE- RIALPR" FOR OUTPUT AS #2 подготовит последовательный принтер для вывода. В языке ассемблера Вы сможете получить доступ к принтеру как с помощью метода управляющего блока файла, так и с помощью метода дескриптора файла, включая очень мощную функцию IOCTL. При этом пользователь имеет возможность доступа к устройству на уров- не операционной системы и может просто ввести команду COPY A:MY- FILE SERIALPR:, чтобы скопировать содержимое файла на принтер. Устанавливаемые драйверы устройств могут быть написаны только на языке ассемблера. Они могут обслуживать два типа устройств: символьные и блочные. Эти имена описывают единицы, которыми уст- ройство обрабатывает данные. Обычно драйверы блочных устройств обслуживают дисковые накопители, а драйверы символьных - все остальное, начиная от последовательных принтеров и кончая робота- ми. Блочные устройства обмениваются блоками данных, поэтому они занимаются накоплением данных. Символьные устройства обмениваются данными побайтно, поэтому они лучше подходят для управляющих устройств, а также для устройств, которые не могут обеспечить высокую скорость обмена данными. Драйверы блочных устройств очень сложны и здесь нет достаточно места, чтобы объяснить их структу- ру. Очень редко кому требуется написать такой драйвер. Техничес- кое руководство по MS DOS предоставляет всю необходимую информа- цию и содержит полный пример драйвера виртуального диска в опера- тивной памяти. Вы можете просмотреть эту информацию после того как изучите обсуждение драйверов символьных устройств, приведен- ное здесь. Устанавливаемые драйверы устройств беспощадны к программистс- ким ошибкам. Поскольку драйверы автоматически загружаются систе- мой при загрузке, то невозможно использовать отладчики для выяв- ления причин неполадок. Поэтому будьте предельно внимательны при их написании. Программа драйвера устройства разбивается на три части, каждая из которых обсуждается отдельно в следующих разделах. Это (1) заголовок драйвера, который именует устройство и содержит инфор- мацию об остальных частях драйвера, (2) стратегия драйвера, кото- рая хранит информацию об области данных, создаваемой MS DOS, которая называетя заголовком запроса, и (3) обработчик прерывания устройства, который и содержит код, управляющий устройством. 7.2.1 Создание заголовка драйвера. Драйверы устройств должны создаваться в виде COM файлов [1.3.6]. Однако они не являются настоящими программами, поскольку у них отсутствует префикс программного сегмента. Чтобы добиться этого не надо включать оператор ORG 100H в начале программы, как это делается для COM файлов. Либо запишите ORG 0, либо вообще ничего не пишите. Драйвер должен быть описан как далекая (far) процедура, как и в любой программе. В нижеприведенном примере приведен начальный код для драйвера устройства с именем DEVICE12. Оно заменяет стандартное устройство AUX, используемое MS DOS, принимая вывод функции 4 прерывания 21H. Весь драйвер устройства состоит из кода этого раздела вместе с кодом, приведенном в сле- дующих двух разделах; поместите их подряд один за другим, чтобы получить полную программу. Драйвер устройства должен начинаться с заголовка драйвера. Он имеет длину 18 байтов, разделенных на 5 полей. Первое поле (DD) всегда содержит значение -1 (FFFFFFFFH), и когда MS DOS загружает драйвер, то оно заменяется на стартовый адрес следующего драйве- ра. Таким образом, система может искать следующий драйвер по цепочке. У последнего загруженного драйвера в этом поле остается значение -1. Второе поле это байт атрибутов драйвера. Имеют значение только 7 битов этого слова: бит 15 1 = символьное устройство, 0 = блочное устройство 14 1 = поддерживает IOCTL, 0 = не поддерживает IOCTL 13 1 = формат блоков IBM, 0 = другой формат блоков 3 1 = часы, 0 = не часы 2 1 = нулевое устройство, 0 = не нулевое устройство 1 1 = устройство стандартного вывода, 0 = нет 0 1 = устройство стандартного ввода, 0 = нет Обычно установлен только бит 15, или биты 15 и 14, если устройст- во поддерживает IOCTL (как обсуждается в [7.2.4]). Бит 13 уста- навливается только для блочных устройств. Остальные биты исполь- зуются для замены устройств, используемых MS DOS по умолчанию (устройствами стандартного ввода и вывода являются клавиатура и видеодисплей; устройство часов объединяет часы реального времени с часами времени суток BIOS; а нулевое устройство (NULL) - это псевдоустройство, используемое для тестовых целей). Третье и четвертое поля содержат смещения для процедур страте- гии и обработки прерывания, которые будут рассмотрены в следующих разделах. Наконец, последнее поле содержит имя устройства. Имя может содержать до 8 символов и оно должно быть выравнено по левому краю с завершающими пробелами. Для замены существующих в DOS устройств, таких как LPT1 или COM1, используйте то же имя устройства, как в данном примере. Низкий уровень. В данном примере создается драйвер для последовательного уст- ройства. "DEVICE12" - имя файла, который должен быть указан в файле конфигурации сиситемы, чтобы этот драйвер был загружен. В байте атрибутов установлен только бит 15, указывая что это сим- вольное устройство и что оно не поддерживает IOCTL. DEV_STRATEGY и DEV_INTERRUPT - имена процедур, обсуждаемых в следующих разде- лах. Устройство названо AUX, с тем чтобы заменить обычное уст- ройство MS DOS с этим именем. Это позволяет очень просто обра- щаться к этому устройству, поскольку система имеет предопределен- ный номер файла для обращения к устройству AUX (последовательно- му). В пример включен начальный код для драйвера, определяющий его как COM программу. CSEG SEGMENT PUBLIC 'CODE' 'устанавливаем кодовый сегмент ORG 0 'эта строка необязательна ASSUME CS:CSEG,DS:CSEG,ES:CSEG DEVICE12 PROC FAR 'драйвер это далекая процедура DD 0FFFFFFFFH 'адрес следующего драйвера DW 8000H 'байт атрибутов DW DEV_STATEGY 'адрес процедуры стратегии DW DEV_INTERRUPT 'адрес процедуры прерывания DB 'AUX ' 'имя устройство (дополненное пробелами) 7.2.2 Создание стратегии устройства. Процедура стратегии устройства требует только пяти строк. Когда система загружает устройство, то она создает блок данных, называемый заголовком запроса. Он имеет две функции. Во-первых он служит областью данных для внутренних операций системы. Более важно то, что заголовок запроса служит областью, через которую происходит обмен информацией между драйвером и вызывающей его программой. Например, когда драйвер выводит данные, то ему дается адрес данных через заголовок запроса. Когда же драйвер завершает свою работу, то он устанавливает в заголовке запроса байт стату- са, который доступен вызывающей программе, тем самым давая воз- можность ей узнать об ошибке. MS DOS создает заголовок запроса при установке драйвера уст- ройства (когда система загружается). Процедура стратегии уст- ройства выполняется только один раз в этот момент. При этом ES:BX указывают на вновь созданный заголовок запроса и процедуре нужно просто скопировать их, чтобы впоследствии он мог быть обнаружен при обращении к драйверу. Адреса смещения и сегмента заголовка помещаются в две переменные. В следующем разделе Вы увидите, что при обращении к драйверу, первое что он делает - восстанавливает значения ES:BX, чтобы можно было получить информацию из заголовка запроса. Размер заголовка запроса может меняться, в зависимости от типа сделанного запроса к драйверу (напр. инициализация, вывод данных или возврат статуса). Однако первые 13 байт заголовка всегда одни и те же. Их формат таков: 1. Длина заголовка запроса (DB). 2. Код устройства (DB). Определяет номер для блочных устройств. 3. Код команды (DB). Здесь хранится номер последней посланной драйверу команды. Эти коды перечислены в [7.2.3]. 4. Статус (DW). Статус устанавливается каждый раз при вызове драйвера. Если установлен бит 15, то в младших восьми битах нахо- дится код ошибки. Коды ошибок перечислены в [7.2.3]. 5. Резервная область (8 байтов). Используется MS DOS. 6. Данные необходимые для работы драйвера (переменной длины). Низкий уровень. Вот 5 строк процедуры стратегии устройства. Отмечаем, что две словные переменные, хранящие значения ES и BX, следуют за инст- рукцией RET, как и положено в формате COM. DEV_STRATEGY: MOV CS:KEEP_ES,ES MOV CS:KEEP_BX,BX RET KEEP_CS DW ? KEEP_BX DW ? 7.2.3 Создание обработчика прерывания устройства. Драйвер устройства начинается с двух порций кода, приведенных в предыдущих разделах. За ними должна следовать соответствующая процедура обработки прерывания. На самом деле, это неверно, назы- вать эту процедуру процедурой обработки прерывания, так как она вовсе не обслуживает прерывание и завершается обычной инструкцией RET. Имеется 13 типов функций, которые может выполнять устанавли- ваемый драйвер устройства. Когда драйвер вызывается функцией DOS (скажем функцией 3FH прерывания 21H, которая читает данные из файла или устройства), то функция помещает кодовый номер от 1 до 13 в однобайтное поле по смещению 2 в заголовке запроса (для ввода - кодовый номер 5). Затем управление передается процедуре обработки прерывания драйвера, адоес которой определяется при просмотре заголовка драйвера [7.2.1]. Эта процедура в первую очередь восстанавливает ES:BX, с тем чтобы они указывали на заго- ловок запроса, а затем читает кодовый номер команды. По этому коду процедура обработки прерывания вызывает нужную процедуру, которая выполнит требуемую функцию. Процедура ищется с помощью 13-словной таблицы, содержащей смещения для 13 типов функций. Функции всегда перечисляются в следующем порядке: 1. INITIALIZE (инициализация) 2. CHECK_MEDIA (проверка носителя) 3. MAKE_BPB 4. IOCTL_IN 5. INPUT_DATA (ввод данных) 6. NONDESTRUCT_IN 7. INPUT_STATUS (статус ввода) 8. CLEAR_INPUT (очистка ввода) 9. OUTPUT_DATA (вывод данных) 10. OUTPUT_VERIFY (проверка вывода) 11. OUTPUT_STATUS (статус вывода) 12. CLEAR_OUTPUT (очистка вывода) 13. IOCTL_OUT После завершения процедуры, процедура обработки прерывания завершается инструкцией RET и управление возвращается в вызываю- щую программу. Драйвер устройства может включать код для обработ- ки только некоторых функций, в зависимости от устройства и тре- буемой степени контроля ошибок и управления устройством. Номера функций, для которых не написаны процедуры, должны завершаться выходом из драйвера без выполнения чего-либо. В этом случае надо только перед выходом установить биты 15, 8, 1 и 0 в заголовке запроса, чтобы информировать вызывающую задачу, что была затребо- вана несуществующая функция (бит 15 индицирует ошибку, бит 8 показывает, что драйвер работает нормально, а биты 0 и 1 дают код ошибки 3, что соответствует "неизвестной команде"). Но одна функция должна присутствовать во всех драйверах уст- ройств, и это функция номер 1 - инициализация. Эта функция авто- матически выполняется при загрузке драйвера, а затем нет. Одна из важных задач, выполняемая этой процедурой, состоит установке адреса конца драйвера в четырех байтах, начинающихся со смещения 14 в заголовке запроса. В нижеприведенном примере конец программы отмечен меткой eop:. Кроме этой задачи, процедура инициализации должна также выполнить всю необходимую для данного устройства инициализацию. На рис. 7-4 показана структура драйвера устройст- ва. Какие из оставшихся 12-ти функций будут включены в драйвер устройства зависит от того, что драйвер должен делать. Некоторые, такие как CHECK_MEDIA и MAKE_BPB, относятся только к блочным устройствам (они устанавливают тип диска, размер секторов и т.д.). Для символьных устройств наиболее важными являются две функции: INPUT_DATA и OUTPUT_DATA (отметим, что эти имена несу- щественны - важна позиция в таблице функций, которая неизменна). В обоих случаях заголовок запроса имеет следующую структуру: 13 байтов стандартный формат заголовка запроса 1 байт байт описания среды (только для блочных устройств) 4 байта смещение/сегмент буфера обмена данных 2 байта число байтов, которое надо передать 2 байта стартовый номер сектора (только для блочных) В нижеприведенном примере используется функция вывода. Процедура, выполняющая вывод получает из заголовка запроса адрес буфера, в котором находятся выводимые данные (смещение 14). Она также счи- тывает число байтов, которое надо вывести (смещение 18). Когда процедура завершит вывод данных, то она установит слово статуса в заголовке запроса (смещение 3) и возвратит управление. Если опе- рация успешна, то надо установить бит 8 слова статуса. Другие возможности будут обсуждены позднее. Низкий уровень. В данном примере приведена общая форма процедуры обработки прерывания, не включая реального кода, управляющего устройством. ;---инициализация обработчика прерывания устройства DEV_INTERRUPT: PUSH ES ;сохраняем регистры PUSH DS PUSH AX PUSH BX PUSH CX PUSH DX PUSH SI PUSH DI PUSH BP MOV AX,CS:KEEP_ES ;ES:BX указывают на заголовок запроса MOV ES,AX ; MOV BX,CS:KEEP_BX ; MOV AL,ES:[BX]+2 ;получаем код команды из заголовка SHL AL,1 ;умножаем на 2 (т.к. таблица словная) SUB AH,AH ;обнуляем AH LEA DI,FUNCTIONS ;DI указывает на смещение до таблицы ADD DI,AX ;добавляем смещение в таблице JMP WORD PTR [DI] ;переходим на адрес из таблицы FUNCTIONS LABEL WORD ;это таблица функций DW INITIALIZE DW CHECK_MEDIA DW MAKE_BPB DW IOCTL_IN DW INPUT_DATA DW NONDESTRUCT_IN DW INPUT_STATUS DW CLEAR_INPUT DW OUTPUT_DATA DW OUTPUT_VERIFY DW OUTPUT_STATUS DW CLEAR_OUTPUT DW IOCTL_OUT ;---выход из драйвера, если функция не поддерживается CHECK_MEDIA: MAKE_BPB: IOCTL_IN: INPUT_DATA: NONDESTRUCT_IN: INPUT_STATUS: CLEAR_INPUT: OUTPUT_VERIFY: OUTPUT_STATUS: CLEAR_OUTPUT: IOCTL_OUT: OR ES:WORD PTR [BX]+3,8103H ;модифицируем статус JMP QUIT ;---процедуры для двух поддерживаемых кодов INITIALIZE: LEA AX,E_O_P ;смещение конца программы в AX MOV ES:WORD PTR [BX]+14,AX ;помещаем его в заголовок MOV ES:WORD PTR [BX]+16,CS ; . (здесь идет инициализация устройства) . JMP QUIT OUTPUT_DATA: MOV CL,ES:[BX]+18 ;получаем число символов CBW CX ;CX используем как счетчик MOV AX,ES:[BX]+16 ;получаем адрес буфера данных MOV DS,AX ; MOV DX,ES:[BX]+14 ; . (здесь идут операции по выводу) . JMP QUIT ;---выходим, модифицируя байт статуса в заголовке запроса QUIT: OR ES:WORD PTR [BX]+3,100H ;устанавливаем бит 8 POP BP ;восстанавливаем регистры POP DI ; POP SI ; POP DX ; POP CX ; POP BX ; POP AX ; POP DS ; POP ES ; RET E_O_P: ;метка конца программы DEVICE12 ENDP CSEG ENDS END DEVICE12 Перед возвратом драйвер устанавливает слово статуса в заголов- ке запроса. В данном примере это делается в двух местах, в зави- симости от того вызывалась функция обеспечиваемая драйвером или нет. Эти строки выглядят так: OR ES:WORD PTR [BX]+3,XXXXH. Значе- ние битов XXXX следующее: биты 0-7 код ошибки (если бит 15 = 1) бит 8 устанавливается в 1, когда функция завершена бит 9 устанавливается в 1, когда драйвер занят биты 10-14 зарезервированы MS DOS бит 15 устанавливается при возникновении ошибки Младший байт этого слова содержит следующие коды ошибок, если установлен бит 15, индицирующий ошибку: 0 попытка записи на защищенное от записи устройство 1 неизвестное устройство 2 устройство не готово 3 неизвестная команда 4 ошибка проверки по контрольной сумме 5 неверная длина запроса к устройству 6 ошибка поиска 7 неизвестный носитель 8 сектор не найден 9 нет бумаги в принтере A ошибка записи B ошибка чтения C общая ошибка 7.2.4 Доступ к драйверу устройства. Драйвер устройства устанавливается путем включения имени гото- вой программы в файл конфигурации системы. Для установки пробной программы поместите в файл CONFIG.SYS строку DEVICE = DEVI- CE12.COM. Затем перезагрузите систему для установки драйвера. Если машина не будет загружаться, то скорее всего имеется ошибка в коде инициализации драйвера. После того как драйвер установлен, для доступа к нему пользуй- тесь обычными функциями MS DOS прерывания 21H. Какие функции можно использовать зависит от того, заменяет ли устройство стан- дартное устройство DOS (как в приведенном примере) или оно добав- ляется как совершенно новое устройство. Для замены стандартного последовательного устройства, назовите драйвер AUX, после чего функции 3 [7.1.7] и 4 [7.1.6] прерывания 21H будут осуществлять соответственно ввод и вывод. Если устройство параллельное, то назовите его PRN, после чего функция 5 [6.3.1] будет выводить данные на принтер. Другой возможностью является использование функции 3FH [5.4.4] для ввода и [5.4.3] для вывода. В этом случае используйте номер файла 3 - для последовательного устройства и 4 - для параллельного. Напоминаем, что при использовании предопре- деленных номеров файла нет необходимости открывать устройство. Если устройство не заменяет одно из стандартных устройств MS DOS (т.е. если оно не названо одним из резервных слов, таким как PRN, AUX и т.д.), то Вы можете открыть устройство с помощью одной из функций для открытия файла. Вы можете использовать как метод доступа с помощью управляющего блока файла, так и метод дескрип- тора файла, хотя последний предпочтительнее. Чтобы быть уверен- ным, что Вы по ошибке не откроете дисковый файл, поместите номер файла в BX, 0 - в AL, посде чего выполните функцию 44H прерывания 21H. Это функция IOCTL и если бит 7 значения, возвращаемого в DL установлен, то драйвер устройства загружен. IOCTL требует, чтобы в байте атрибутов драйвера была соот- ветствующая установка битов и чтобы по крайней мере основы проце- дуры обработки IOCTL имелись в процедуре обработчика прерывания драйвера. Функция IOCTL имеет 8 подфункций, пронумерованных от 0 до 7, при этом соответствующий кодовый номер помещается в AL при вызове функции: 0 Возвратить информацию об устройстве в DX 1 Установить информацию об устройстве, используя DL (DH=0) 2 Считать CX байтов от драйвера устройства через управля- щий канал и поместить их начиная с DS:DX 3 Записать CX байтов в драйвер устройства через управляющий канал, взяв их начиная с DS:DX 4 То же, что и 2, но использовать номер накопителя в BL, где 0 = накопитель по умолчанию, 1 = A и т.д. 5 То же, что и 3, но использовать номер накопителя как в 5 6 Получить статус ввода 7 Получить статус вывода В ответ возвращается различная информация, в зависимости от того, какая функция вызвана. Для подфункций 0 и 1 значение битов регистра DX следующее (при условии, что бит 7 = 1, что означает, что доступ получен к устройству, а не к файлу): 0 1 = устройство консольного ввода 1 1 = устройство консольного вывода 2 1 = нулевое устройство 3 1 = устройство часы 4 резерв 5 1 = нет проверки на Ctrl-Z, 0 = есть проверка на Ctrl-Z 6 1 = не конец файла, 0 = конец файла 7 1 = устройство, 0 = дисковый файл 8-13 резерв 14 1 = если можно использовать подфункции 2 и 3, 0 = нельзя 15 резерв Подфункции 2-5 позволяют программе и устройству обмениваться произвольными управляющими строками. Это позволяет передавать управляющие сообщения отдельно от основного потока данных, что существенно упрощает дело. При возврате AX будет содержать число переданных байтов. Подфункции 6-7 позволяют программе проверить, готово ли устройство для ввода или вывода. Для устройств в AL возвращается FF, если устройство готово и 0, если нет. При ис- пользовании с открытым файлом (бит 7 = 0) в AL возвращается FF до тех пор, пока не будет доститгнут конец файла. Отметим, что в Бейсике 3.0 добавлены операторы IOCTL и IOCTL$. Они позволяют бейсиковской программе, соответственно, посылать и принимать управляющие строки от драйвера устройства, которое было предварительно открыто оператором OPEN. Выходная строка должна быть заключена в кавычки, как в IOCTL #3,"...". Подобным образом, A$ = IOCTL$(3) принимает информацию о статусе через IOCTL. 7.2.5 Обнаружение и анализ ошибок устройства. Устройства могут ошибаться по одной из трех причин. Устройство может быть физически повреждено или находиться не в том состоя- нии. Может быть плохим программное обеспечение, управляющее уст- ройством. И, наконец, программа может послать устройству недопус- тимый запрос (например, попытка писать на накопитель, где нахо- дится дискета защищенная от записи). MS DOS обнаруживает и анали- зирует большинство таких ошибок и обеспечивает возможности для восстановления. Высокий уровень. Интерпретатор Бейсика обнаруживает многие ошибки, включая ошибки драйверов устройств. При обнаружении ошибки возвращается код ошибки и если не предусмотрена программа восстановления при ошибках, то программа останавливается. Однако можно установить обработку ошибок, с тем чтобы когда происходит критическая ошибка Бейсик автоматически переходил на процедуру восстановления при сбоях, которую Вы создали. Процедура может проанализировать код и определить в какой строке программы произошла ошибка. После того как это сделано, программа может принять меры по устранению ошиб- ки, либо с помощью пользователя, либо выполняя другую часть прог- раммы. После того, как эта процедура завершена, программа может продолжить выполнение с любого места, с которого Вы захотите (с некоторыми ограничениями). Код для тщательного анализа ошибочных ситуаций может существенно увеличить размер программы. Отметим, что компилятора Бейсика даже минимальные проверки на ошибки пот- ребуют дополнительно по не менее чем 4 байта на каждую строку программы. Чтобы разрешить обработку ошибок в Бейсике поместите в начале программы строку ON ERROR GOSUB n, где n это номер строки прог- раммы, в которой начинается процедура обработки ошибок. При воз- никновении критической ошибки управление будет передано на эту строку. В начале процедуры поместите ряд строк вида IF ERR = n THEN номерстроки, где n - номер ошибки, взятый из приложения к руководству по Бейсику, содержащему сообщения об ошибках. Номера строк в этих операторах соответствуют началу кода, обрабатывающе- го данную конкретную ошибку. Эти части могут быть в свою очередь разбиты на куски рядом операторов IF ERL = n THEN номерстроки. ERL возвращает номер строки, в которой произошла ошибка, позволяя процедуре восстановления точно определить ошибочное место. После того как процедура восстановления завершила свою работу надо использовать оператор RESUME для возврата управления в ту строку, где произошла ошибка. За этим оператором может следовать номер, в этом случае управление будет передано на строку с ука- занным номером. Однако, имейте ввиду, что нельзя использовать RESUME для перехода в точку программы, которая находится за пре- делами процедуры, в которой произошла ошибка. Если восстановление после ошибки невозможно, но необходимо, чтобы программа продолжи- ла свою работу, то напишите RESUME NEXT и управление будет пере- дано на строку, следующую за той, в которой произошла ошибка. Вот общая структура процедуры восстановления в Бейсике: 100 ON ERROR GOSUB 5000 'разрешаем обработку ошибок . . 5000 IF ERR = 61 THEN 5100 'диск полон 5010 IF ERR = 71 THEN 5200 'диск не готов . . 5100 IF ERL = 2080 THEN 5120 'где произошла ошибка? 5110 BEEP: PRINT "Disk in drive B: is full": RESUME 5120 BEEP: PRINT "Disk in drive A: is full": RESUME . 5200 BEEP: PRINT "A disk drive is not ready" 5210 PRINT "Strike any key when corrected" 5220 IF INKEY$ = "" THEN 5220 'ожидаем нажатия клавиши 5230 RESUME ERL - 10 'пытаемся повторить операцию В Бейсике 3.0 введены инструкции ERDEV и ERDEV$. Обе они поз- воляют получить переменные только для чтения от прерывания 24H, обрабатывающего критичекие ошибки. Z% = ERDEV возвращает в Z% слово статуса, в котором старший байт содержит 13-15 биты атрибу- та заголовка устройства, а младший байт - код ошибки прерывания 24H. Z$ = ERDEV$ помещает в Z$ 8-байтное имя устройства для сим- вольных устройств и 2-байтный указатель накопителя для блочных устройств. Низкий уровень. Иногда драйверы устройств содержат такие серьезные ошибки, что программа просто не может продолжаться, пока они не будут исправ- лены. Когда такие ошибки происходят, то система вызывает обработ- чик критических ошибок. Он может вступать в действие как для стандартных устройств, так и для установленных драйверов. Пользо- ватель наиболее часто сталкивается с ним, когда пытается произ- вести дисковую операцию с дисководом, у которого открыта дверца. В этом случае появляется сообщение: "Not ready error reading drive A - Abort, Retry, Ignore?" Обработчик критических ошибок может быть переписан, чтобы он лучше обрабатывал устройства, для которых Вы создали устанавли- ваемые драйверы. Вектор прерывания 24H указывает на стандартную процедуру MS DOS, но Вы можете перенаправить вектор на свою про- цедуру. При вызове этой процедуры старший бит AH содержит 0 если ошибка произошла на блочном устройстве и 1, если на символьном. BP:SI указывают на заголовок драйвера виновного устройства, кото- рый может дать дополнительную информацию. Восемь байтов, начиная со смещения AH в заголовке содержат имя устройства, а обработчик критичеких ошибок помещает код ошибки длиной в слово в DI. Вот кодовые номера (они не представляют битовых позиций): Код Проблема 0 попытка писать на диск, защищенный от записи 1 неизвестное устройство 2 накопитель не готов 3 неизвестная команда 4 ошибка обмена данными 5 неверная длина запроса 6 ошибка поиска 7 неизвестный тип носителя 8 сектор не найден 9 нет бумаги в принтере A ошибка при записи B ошибка при чтении C общая ошибка В случае дисковой ошибки AL содержит номер накопителя, на котором произошла ошибка (0 = A, 1 = B и т.д.), а биты 2-0 AH индицируют тип ошибки. Бит 0 устанавливается, если ошибка произошла во время операции записи, и сбрасывается - если при чтении. Биты 2-1 со- держат информацию о том, в каком месте диска произошла ошибка, давая 00 - для начальных секторов DOS, 01 - для FAT, 10 - для каталога и 11 - для всего остального диска. Имеется три способа, которыми программа может восстановиться после критической ошибки: 1. Можно попросить пользователя устранить причину ошибки (напри- мер, закрыть дверцу накопителя), после чего система предоставит устройству возможность повторить операцию. 2. Управление может быть возвращено инструкции, следующей за INT 21H, которая сделала попытку обратиться к драйверу. 3. Программа может завершиться и вернуть управление системе. Ваша процедура обработки ошибок может восстановить ситуацию, выдав инструкцию IRET, после того, как она поместила 0 в AL, чтобы игнорировать ошибку, 1 - чтобы повторить операцию и 2 - чтобы завершить программу. Если Вы хотите, чтобы Ваша процедура провела восстановление сама, то она должна восстановить регистры выполняемой программы из стека, а затем удалить со стека все, кроме последних трех слов. После этого инструкция IRET возвратит управление программе, хотя сама система останется в нестабильном состоянии до тех пор, пока она не сделает вызов функции с номером большим, чем 12. Вот конфигурация стека (начиная сверху до низа) когда вызывается обработчик критических ошибок: Адрес возврата обработчика ошибок: IP, CS, флаги Пользовательские регистры задачи, AX, BX, CX, DX, SI, DI, BP, из которой был вызван драйвер: DS, ES, IP, CS, флаги MS DOS обрабатывает также многие некритические ошибки. Сюда включаются коды ошибок, которые могут возвращаться в регистрах, когда вызывалась функция DOS. Эти коды обсуждаются в данной книге в тех местах, в которых описываются соответствующие функции. Однако имейте ввиду, что начиная с версии 3.0 MS DOS возвращает расширенные коды ошибок для функций, использующих FCB или деск- рипторы файлов. Когда при выполнении одной из этих функций уста- навливается флаг переноса, то в AX возвращается обычный код ошиб- ки. Дополнительный расширенный код доступен через прерывание 59H, если в BX поместить 0. Эта функция сообщает также о критических ошибках и она может быть использована из обработчика критических ошибок, вызываемого через прерывание 24H. Функция помещает в AX код ошибки, взятый из обычного списка знакомых кодов ошибок (например, "недостаточно памяти") или один из новых кодов (например, "ограничение доступа" для многопользо- вательской системы). BH возвращает код класса ошибки, указывая какого типа ошибка произошла. Например, код 1 указывает, что исчерпаны ресурсы, т.е. что память, файловые буфера или что-то еще израсходовано. Другие классы могут указывать на программные ошибки, проблемы с носителями, формати