21H ;открываем файл JC OPEN_ERROR ;проверка на ошибку MOV HANDLE,AX ;сохраняем номер файла ;---вычисляем позицию записи и устанавливаем файловый указатель MOV AX,30 ;размер записи 30 байтов MOV CX,54 ;номер записи #54 (55-я запись) MUL CX ;теперь смещение для нее в DX:AX MOV CX,DX ;помещаем старшее слово в DX MOV DX,AX ;помещаем младшее слово в CX MOV AL,0 ;устанавливаем указатель на начало MOV AH,42H ;функция установки указателя MOV BX,HANDLE ;номер файла INT 21H ;устанавливаем указатель JC POINTER_ERROR ;проверка на ошибку ;---пишем запись с прямым доступом MOV AH,40H ;номер функции MOV BX,HANDLE ;номер файла MOV CX,30 ;размер записи LEA DX,REC_BUFFER ;DS:DX указывают на буфер INT 21H ;пишем запись JC WRITE_ERROR ;проверка на ошибку В отличии от метода FCB метод дескриптора файлов не предостав- ляет специальной функции для записи блока записей прямого досту- па. Однако Вашей программе необходимо только вычислить количество байтов, составляющих блок записей, которое должно быть записано. 5.4.6 Чтение из файлов прямого доступа. Чтение файлов прямого доступа является обратным процессом по отношению к их записи. MS DOS вычисляет позицию в файле на диске, затем считывает запись и помещает ее в память. Затем программа должна разделить запись на поля в точности того же размера, кото- рый был использован при конструировании записи. Не забудьте уда- лить символы пробела, добавленные при заподнении полей. Обсужде- ние записи данных в файлы прямого доступа [5.4.5] содержит инфор- мацию, которая поможет Вам лучше понять информацию данного разде- ла. Высокий уровень. Для чтения файла прямого доступа необходимо открыть файл и определить поля записи, как объяснено в разделе, относящемся к записи в файлы прямого доступа. Затем надо использовать оператор GET# для чтения определенной записи с диска. GET #1,23 считывает запись номер #23 из файла, открытого под номером #1. При чтении записи переменной, именованной в операторе FIELD, автоматически присваивается соответствующее значение из записи. Например, если оператор FIELD имеет вид FIELD 1, 20 AS X$, 2 AS Y$, то после выполнения оператора GET 1,23 переменной X$ будет присвоено зна- чение первых 20-ти байтов записи 23, а переменной Y$ - следующих 10-ти байтов. Операторы, аналогичные RSET и LSET для выделения полей данных отсутствуют. В случае числовых полей, напоминаем, что они должны быть преобразованы в строковый вид с помощью функций MKI$, MKS$ и MKD$. Для восстановления их оригинальных значений, с тем чтобы над ними можно было проводить операции и печатать их, надо преоб- разовать эти строки с помощью функций CVI, CVS и CVD. Если Y$ содержит целое число, то для выполнения обратного преобразования запишите Y% = CVI(Y$), при этом переменная Y% будет содержать значение, которое имела переменная перед тем как она была спе- циально обработана для записи в файл прямого доступа. Если Вы выведете строковое значение переменной, то увидите, что это число в интервале от 0 до 65535, закодированное в два символа ASCII. В данном примере открывается файл, созданный в примере пункта [5.4.5], и выводится содержимое любой из затребованных записей: 100 OPEN "A:NEWDATA" AS #1 LEN = 24 'открываем файл 110 FIELD 1, 18 AS LASTNAME$, 2 AS AGE$, 4 AS WEIGHT$ 120 CLS: INPUT "What is the record number";R 'запрос записи 130 IF R*24 > LOF(1) THEN BEEP: PRINT"No such record": GOTO 120 140 GET #1,R 'читаем запись из файла 150 PRINT LASTNAME$, CVI(AGE$), CVS(WEIGHT$) 'выводим ее 160 PRINT: PRINT "Do another (y/n)?" 'будем повторять? 170 C$ = INKEY$: IF C$ = "" THEN 170 'ожидаем ввода 180 IF C$ = "y" OR C$ = "Y" THEN 120 'повторяем, если надо 190 CLOSE 'иначе закрываем файл Средний уровень. Метод FCB доступа к файлам имеет две функции для чтения запи- сей с прямым доступом. С другой стороны, метод дескриптора файлов использует ту же функцию, что и для чтения последовательных фай- лов. Два метода доступа рассматриваются отдельно. Метод FCB: Функция 21H прерывания 21H читает одну запись из файла прямого доступа. Вторая функция, 27H, читает блок последовательных запи- сей. Создайте управляющий блок файла, как показано в [5.3.5] и откройте его [5.3.3]. После того как FCB открыт, введите в него значения полей размера записи (DW по смещению 14) и номера записи прямого доступа (DD по смещению 33). Если DS:DX указывают на первый байт FCB, то можно вызывать функцию 21H для чтения записи и запись будет помещена в паямть, начиная с первого байта DTA. Если запись успешно прочитана, то в AL будет возвращен 0. Однако при этом нет гарантии, что чтение прошло без ошибок, пос- кольку неверный размер записи может привести к тому, что части прилегающих записей будут считаны, как будто это одна запись. Если запрошена запись с номером большим, чем число записей в файле, то в AL будет возвращено 1 или 3. Если был возвращен код 3, то был считан самый конец файла и была прочитана часть записи данных. Если был возвращен код 1, то данные вообще не были счита- ны. Данный пример считывает одну запись и помещает ее в DTA: ;---в сегменте данных FCB DB 1,'OLDDATA ', 25 DUP (0) ;---открываем файл и устанавливаем поля FCB MOV AH,0FH ;номер функции LEA DX,FCB ;DS:DX указывают на FCB MOV BX,DX ;копируем смещение FCB INT 21H ;открываем файл MOV AX,55 ;размер записи 55 байтов MOV [BX]+14,AX ;помещаем в поле размера записи MOV AX,22 ;номер записи для чтения MOV [BX]+33,AX ;помещаем в поле номера записи MOV AX,0 ;обнуляем старшее слово этого поля MOV [BX]+35,AX ; ;---перенос данных из файла в DTA MOV AH,21H ;номер функции чтения с прямым доступом LEA DX,FCB ;DS:DX указывают на FCB INT 21H ;читаем данные, помещая их в DTA CMP AL,0 ;проверка на ошибку JNE READ_ERROR ; ;---позднее, закрываем файл MOV AH,10 ;номер функции закрытия файла LEA DX,FCB ;DS:DX указывают на FCB INT 21H ;закрываем файл Для чтения блока последовательных записей в память за один прием надо использовать функцию 27H прерывания 21H. Ее выполнение подготавливается в точности так же, как и функции 21H, за исклю- чением того, что вдобавок CX должен содержать число записей кото- рые надо прочитать за один прием. При возврате CX будет содержать число реально прочитанных записей. Значения возвращаемые в AL совпадают с теми, которые возвращаеются функцией 21H. В отличии от функции 21H поля FCB, в которых хранится информация о положе- нии записи (поле записи прямого доступа, текущего блока и текущей записи) автоматически увеличиваются, с тем чтобы они указывали на следующую несчитанную запись после выполнения функции. Отметим, что как в случае чтения одной, так и в случае чтения нескольких записей, поля текущего блока и текущей записи FCB устанавливаются по значению поля записи прямого доступа. Если Вы знаете значение текущего блока и текущей записи, а не соответст- вующий номер записи прямого доступа, то используйте функцию 24H прерывания 21H, чтобы она проделала вычисления за Вас. У этой функции нет входных регистров, надо только, чтобы DS:DX указывали на открытый FCB. При возврате поле записи прямого доступа будет заполнено значением, соответствующим установке двух других полей. Метод дескриптора файлов: В предыдущем разделе показано, как писать записи прямого дос- тупа с помощью метода дескриптора файлов. Процедура чтения из файла с прямым доступом подготавливается совершенно аналогичным образом, путем вычисления смещения в файле, на которое должен указывать файловый указатель. DS:DX должны указывать на буфер, в который будет помещена запись, после чего надо выполнить функцию 3FH прерывания 21H. При входе CX должен содержать размер записи, а BX - номер файла. ;---в сегменте данных HANDLE DB ? FILEPATH DB 'A:OLDDATA',0 REC_BUFFER DB 30 DUP(?) ;---открываем файл MOV AH,3DH ;номер функции MOV AL,0 ;код открытия для чтения LEA DX,FILEPATH ;DS:DX указывают на путь к файлу INT 21H ;открываем файл JC OPEN_ERROR ;проверка на ошибку MOV HANDLE,AX ;запоминаем номер файла ;---вычисляем позицию записи и устанавливаем файловый указатель MOV AX,30 ;размер записи MOV CX,54 ;читаем запись #54 (55-ю запись) MUL CX ;смещение записи в DX:AX MOV CX,DX ;помещаем старшее слово смещения в DX MOV DX,AX ;помещаем младшее слово смещения в CX MOV AL,0 ;устанавливаем указатель на начало файла MOV AH,42H ;функция установки указателя MOV BX,HANDLE ;номер файла INT 21H ;устанавливаем указатель JC POINTER_ERROR ;обработка ошибки ;---читаем запись с прямым доступом MOV AH,3FH ;номер функции MOV BX,HANDLE ;номер файла MOV CX,30 ;размер записи LEA DX,REC_BUFFER ;DS:DX указывают на буфер для записи INT 21H ;читаем запись JC READ_ERROR ;обработка ошибки ;---позднее, закрываем файл MOV BX,HANDLE ;номер файла MOV AH,3EH ;функция закрытия файла INT 21H ;закрываем файл JC CLOSE_ERROR ;проверка на ошибку 5.4.7 Проверка данных после операций чтения/записи. MS DOS может проверять правильность производимого обмена с диском прямо во время обмена. Ошибки происходят настолько редко, что средства проверки обычно не используются, чтобы не замедлять обмен с диском. Однако, если это необходимо, то имеется два спо- соба проверки. Один состоит во включении команды VERIFY = ON в файл CONFIG.SYS, который автоматически читается при загрузке операционной системы. Впоследствии, все дисковые операции будут проверяться. Это единственный способ проверки доступный в Бейси- ке. Второй метод состоит использовании специальной функции DOS для верификации только критических дисковых операций. Если проце- дура верификации обнаруживает ошибку, то она сообщает об условии критической ошибки, как описано в [7.2.5]. Средний уровень. Функция 2EH прерывания 21H включает и выключает проверку. Поместите в AL 1 - для включения верификации и 0 - для выключе- ния. DL также должно быть равно 0. Затем надо выполнить прерыва- ние. У этой функции нет выходных регистров. ;---включение верификации MOV AL,1 ;номер кода MOV DL,0 ;необходимый входной регистр MOV AH,2EH ;номер функции INT 21H ;включаем проверку Для определения текущего режима верификации надо вызвать функ- цию 54H прерывания 21H. У нее нет входных регистров. При возврате AL = 1, если проверка включена и AL = 0, если выключена. 5.4.8 Определение дисковых ошибок и восстановление после них. Дисковые операции настолько сложны, что имеется большое коли- чество возможных ошибок. Большинство дисковых ошибок обсуждаются вместе с операциями, при которых они могут происходить. В данном разделе они собраны вместе, чтобы помочь Вам при разработке про- цедуры общего назначения для восстановления после дисковых оши- бок. Дисковые ошибки бывают двух типов, которые мы будем называть мягкими (soft) и жесткими (hard). Мягкие ошибки возникают из-за неправильного запроса на доступ к файлу: запрошенный файл может отсутствовать или дисковое пространство может кончиться прежде, чем будет записан весь файл. С другой стороны, жесткие ошибки возникают при неверных последовательностях или временных несоот- ветствий при дисковых операциях, которые могут быть следствием неверного выравнивания или проблем с накопителем. В этом случае, лучше всего произвести сброс диска перед обработкой. Высокий уровень. В [7.2.5] объяснено как подготовить процедуру обработки оши- бок. Оператор ON ERROR GOSUB заставляет программу перейти на процедуру обработки ошибки при возникновении критической ошибки. Процедура прежде всего определяет кодовый номер ошибки в Бейсике, который для дисковых ошибок может быть одним из следующих: 52 Bad file number. (Неверный номер файла.) Файл не отк- рыт под тем номером, к которому идет обращение (#1, #2 и т.д.) 53 File not found. (Файл не найден.) Используется при выполнении операторов LOAD, KILL, NAME, FILES и OPEN. 54 Bad file mode. (Неверный режим доступа.) Попытка дос- тупа к файлу другим образом, по сравнению с тем, для чего он был открыт, например, попытка записи в после- довательный файл, открытый для чтения. 55 File already open. (Файл уже открыт.) Попытка открыть файл, который уже открыт, или уничтожить (KILL) файл, который еще не закрыт. 58 File already exists. (Файл уже существует.) Попытка переименовать файл (с помощью NAME) на имя, которое уже есть в каталоге. 61 Disk full. (Диск полон.) См. специальное обсуждение в [5.1.4], относящееся к этой ошибке. 62 Input past end. (Чтение за концом файла.) Попытка прочитать из последовательного файла больше перемен- ных, чем он содержит. Чтобы избежать этой ошибки ис- пользуйте функцию EOF, как объяснено в [5.4.4]. 63 Bad record number. (Неверный номер записи.) Попытка прочитать или записать запись с номером большим, чем число записей в файле. 64 Bad file name. (Неверное имя файла.) Используется операторами KILL, NAME и FILES. 67 Too many files. (Слишком много файлов.) В каталоге больше нет места для записи информации о файлах. Дру- гой возможный вариант состоит в том, что открытие еще одного файла приведет к тому, что будет превышено максимально допустимое число одновременно открытых файлов. 70 Disk is write-protected. (Диск защищен от записи.) 71 Disk is not ready. (Диск не готов.) Наиболее вероятно, не закрыт дисковод с дискетой. 72 Disk media error. (Диск поврежден.) Как правило, это сообщение выдается при повреждении дискеты, однако иногда оно появляется при сбоях оборудования. 74 Specified wrong disk in RENAME operation. (Указан неверный диск в операции RENAME.) 75 Path/file access error. (Ошибка доступа к файлу.) Попытка открыть подкаталог или метку тома, как файл. Или попытка писать в файл, который защищен от записи. Эта ошибка чаще всего выдается при попытке удалить текущий каталог. Появляется при операциях OPEN, NAME, MKDIR, CHDIR и RMDIR. 76 Path not found. (Путь не найден.) Неправильно указан путь или его не существует. Появляется при операциях OPEN, MKDIR, CHDIR и RMDIR. После того как процедура распознала ошибку, необходимо инфор- мировать об ошибке пользователя. Когда пользователь сообщит, что причина ошибки устранена, то оператор RESUME посылает программу назад на ту строку, где произошла ошибка. Оператор RESUME может сопровождаться номером строки, поэтому программа может вернуться к началу всей последовательности дисковых операций, независимо от того, в какой строке произошла ошибка (отметим, что файлы не закрываются при возникновении ошибки). В следующем примере прог- рамма позволяет восстановить ситуацию после ошибок, связанных с переполнением диска и защитой от записи: 100 ON ERROR GOSUB 5000 ' . . 600 ''' . . 5000 ''' 5010 IF ERR = 61 PRINT "Disk full": GOTO 5100 5020 IF ERR = 70 PRINT "Disk is write protected": GOTO 5100 . . 5100 PRINT "Correct the problem, then strike any key" 5110 C$ = INKEY$: IF C$ = "" THEN 5110 5120 RESUME 600 Средний уровень. Функция 1 прерывания 13H возвращает в AL байт, дающий статус дискового накопителя. Значение его битов следующее: биты 0-1 01 = неверная команда, или, если бит 3 = 1, то попытка обмена данными за границей 64K 10 = адресная метка не найдена 11 = попытка записи на защищенный от записи диск 2 1 = указанный сектор не найден 3 1 = переполнение DMA (потеря данных при обмене), или, если бит 0 = 1, то попытка обмена дан- ными за границей 64K 4 1 = данные прочитаны неверно, надо повторить 5 1 = ошибка контроллера 6 1 = ошибка операции поиска 7 1 = нет ответа от накопителя (тайм-аут) Каждая из функций обращения к диску MS DOS использует только некоторые из возможных кодов ошибок, а некоторые функции не сооб- щают об ошибке. Однако во всех случаях при возникновении ошибки устанавливается флаг переноса. Если произошла ошибка, то номер кода этой ошибки возвращается в AX. Вот коды, относящиеся к дис- ковым операциям: 1 Неверный номер функции 2 Файл не найден 3 Путь не найден 4 Уже открыто максимально допустимое число файлов 5 Отрицание доступа (ошибка оборудования) 6 Неверный номер файла 15 Указан неверный накопитель 16 Попытка удалить текущий каталог 17 Не то же устройство 18 Больше нет файлов (при поиске в каталоге с использова- нием джокеров) Восстановление после этих "мягких" ошибок несложно. Некоторые предупреждают Вас о программных ошибках. Другие возникают из-за ошибочных действий пользователя. Если же не отвечает сам накопи- тель, то произошла критическая ошибка. В разделе [7.2.5] показано как написать процедуру обработки критических ошибок. В MS DOS 3.0 введены расширенные коды ошибок. Они могут быть получены с помощью функции 59H прерывания 21H, когда флаг перено- са индицирует возникновение ошибки. Обсуждение этого вопроса см. в [7.2.5]. Глава 6. Принтер. Раздел 1. Управление работой принтера. MS DOS может работать с тремя параллельными устройствами (LPT1 - LPT3) и в этой главе показано как управлять ими. Последователь- ные принтеры управляются в точности так же, как и параллельные, за исключением способа, которым данные посылаются на принтер; эта информация приведена в разделе 1 главы 7. Каждое параллельное устройство имеет свой адаптер. Адаптер управляется тремя регист- рами ввода/вывода и адреса портов этих регистров различны для каждого адаптера. Область данных BIOS содержит базовые адреса для каждого адаптера. Базовый адрес соответствует младшему адресу группы из трех адресов портов. Базовый адрес для LPT1 - 0040:0008, для LPT2 - 0040:000A и т.д. Какой адаптер назначен какому номеру LPT - не определено , как видно из нижеприведенной таблицы. По этой причине программа, котрая прямо адресуется в параллельный порт, должна выискивать адреса, которые он исполь- зует. Отметим, что при инициализации базовому адресу присваивает- ся значение 0, когда соответствующий адаптер не установлен. Адаптер Выходных данных Статуса Управления Монохромная карта (PC/XT/AT) 3BCH 3BDH 3BEH Адаптер принтера PC/XT Адаптер принтера PCJr 378H 379H 37AH Последовательная/параллельная карта AT (установленная как LPT1) Последовательная/параллельная 278H 279H 27AH карта AT (установленная как LPT2) Регистр выходных данных - это тот адрес порта, через который проходит каждый байт данных, посылаемый в принтер. Регистр стату- са сообщает различную информацию о принтере; процессор может постоянно опрашивать его, чтобы распознать момент, когда все в порядке и можно посылать данные. Регистр статуса сообщает также, что произошла ошибка на принтере. Регистр управления инициализи- рует адаптер и управляет выводом данных. Он может также подготав- ливать параллельный порт для операций прерывания, с тем чтобы принтер посылал прерывание к процессору, когда он готов к приему очередного символа, оставляя процессор свободным для других дел. Вот значение битов регистров статуса и управления: Регистр управления бит 0 0 = нормальная установка, 1 = вызывает вывод байта данных 1 0 = нормальная установка, 1 = автоматический перевод строки после возврата каретки 2 0 = инициализировать порт принтера, 1 = нормальная установка 3 0 = отмена выбора принтера, 1 = нормальная установка 4 0 = прерывание принтера запрещено, 1 = разрешено 5-7 не используются Регистр статуса бит 0-2 не используются 3 0 = ошибка принтера, 1 = нет ошибки 4 0 = принтер off-line, 1 = принтер on-line 5 0 = бумага вставлена, 1 = нет бумаги 6 0 = принтер подтверждает прием символа, 1 = нормаль- ная установка 7 0 = принтер занят, 1 = принтер свободен Не имеется никаких оснований, чтобы любая программа не имела процедуру восстановления при ошибках, возникающих при работе с принтером. Хорошо написанная программа должна начинать с проверки того, что принтер связан с машиной (on line). Если присоединен не один принтер, то программа должна позволять пользователю выбрать с каким из них он будет работать. Кроме того, эта процедура долж- на восстанавливать ситуацию при любых ошибках принтера, при этом хотелось бы, чтобы не было необходимости снова печатать весь документ. 6.1.1 Инициализация порта принтера/повторная инициализация принтера. Программы должны инициализировать порт каждого принтера (LPT1 - LPT3) перед первым использованием принтера. Порты принтера должны также повторно инициализироваться после устранения причин ошибки принтера. Не путайте инициализацию порта принтера с ини- циализацией самого принтера. Инициализация принтера это внутрен- нее дело принтера. Она происходит автоматически при его включе- нии и в большинстве случаев принтер не может быть повторно ини- циализирован без его выключения и повторного включения. Но прог- рамма может повторно инициализировать принтер, в том смысле, что могут быть восстановлены начальные параметры, которые принтер использует для печати, отменяя все специальные шрифты, остановы табуляции и т.д. Считается правилом хорошего тона производить такой сброс принтера, когда программа завершает работу с ним. Языки высокого уровня инициализируют порт принтера автомати- чески, но программы на языке ассемблера требуют для этой цели короткую процедуру. С другой стороны, восстановление начальных параметров печати требуется во всех программах. Некоторые принте- ры, такие как новые Эпсоновские принтеры, имеют "главный код сброса", который приводит к полному сбросу принтера. Но поскольку не все принтеры имеют такой код, то программа должна предусматри- вать в своей завершающей части восстановление всех измененных параметров. Например, она может подать коды выключения курсива, выключения плотной печати и т.д. Не забудьте включить вызов этой процедуры в процедуру выхода по Ctrl-Break. Имейте в виду, что на многих принтерах символы не печатаются до тех пор, пока не получен код возврата каретки, завершающий строку (или до тех пор пока не введена целая строка данных). Символы могут спокойно ожидать в буфере принтера, даже после того, как породившая их программа завершилась. Когда начинается новая передача данных на принтер, то эти символы будут напечата- ны. Чтобы избежать этой проблемы, не забывайте почистить буфер перед началом печати; а в качестве правил хорошего тона, чистите буфер также при завершении программы. Это делается посылкой на принтер кода ASCII 24 (при этом параметры печати не меняются). Средний уровень. Функция 1 прерывания 17H BIOS инициализирует порт принтера и возвращает байт, дающий статус порта. Поместите в DX номер порта - число от 0 до 2 для LPT1 - LPT3, после чего вызовите прерыва- ние. Байт статуса принтера (идентичный обсуждаемому в [6.1.2]) возвращается в AH. ;---инициализация LPT1 MOV AH,1 ;функция инициализации принтера MOV DX,0 ;LPT1 INT 17H ;проводим инициализацию Низкий уровень. Ренистр управления выводом каждого адаптера принтера имеет бит, который вызывает инициализацию адаптера. Этот регистр имеет адрес порта на 2 больше, чем базовый адрес адаптера. Напоминаем, что базовый адрес для LPT1 хранится в ячейке 0040:0008, для LPT2 - в 0040:000A и т.д. Имеют значение только младшие 5 битов ре- гистра управления выводом. Бит 2 - бит инициализации принтера и обычно он устанавливается в 1. Для инициализации адаптера надо сбросить этот бит в 0 на тысячу тактов пустого цикла (3000 для AT или на 1/20 секунды, используя счетчик времени суток BIOS [2.1.5]). В этот момент нужно, чтобы был установлен только бит 3 (принтер выбран). Поэтому пошлите в порт значение 12, сделайте задержку, а затем пошлите в порт обычное (без прерываний) неини- циализонное значение, которое равно 8. В данном примере инициализируется LPT1: ;---инициализируем LPT1 MOV DX,ES:[8] ;считываем базовый адрес в DX INC DX ;прибавляем 2 к базовому адресу INC DX ; MOV AL,12 ;значение для инициализации OUT DX,AL ;начинаем инициализацию DELAY: MOV AX,1000 ;начало пустого цикла DEC AX ;уменьшаем счетчик JNZ DELAY ;повторяем 1000 раз MOV AL,8 ;обычное значение для регистра OUT DX,AL ;конец инициализации 6.1.2 Проверка того, что принтер связан с машиной. Программа всегда должна проверить, что принтер связан с маши- ной, перед тем, как послать на него вывод. Легко установить, что принтер не готов, так как бит 3 регистра статуса принтера уста- навливается в 1 в этом случае. Но намного сложнее точно опреде- лить почему принтер не готов: выключен ли он, отменен выбор прин- тера или в нем нет бумаги. Это происходит из-за того, что принте- ры разных производителей посылают разные наборы битов в регистр статуса принтера, даже когда они находятся в идентичном состоя- нии. Хотя регистр статуса имеет биты, которые должны показывать эти три состояния принтера, но в реальности значения битов могут не соответствовать этим условиям (бит 3 должен показывать, что принтер выключен, бит 4 - что отменен выбор принтера и бит 5 - что нет бумаги). Нижеприведенные значения возвращаются в регистр статуса по стандарту "Эпсон", которому обычно следует IBM: Значение Цепочка битов Интерпретация 223 11011111 принтер готов 87 01010111 принтер не готов 119 01110111 нет бумаги в принтере 247 11110111 принтер выключен Регистр статуса ввода имеет адрес порта на 1 больше, чем базо- вый адрес принтера. Базовый адрес для LPT1 хранится по адресу 0040:0008, для LPT2 - по адресу 0040:000A и т.д. Имейте в виду, что если принтер был выключен, то ему требуется некоторое время на инициализацию после включения. Не начинайте печатать до тех пор, пока регистр статуса ввода не сообщит, что принтер связан с машиной и готов к приему данных. Высокий уровень. Данная процедура проверяет связан ли принтер с машиной и гово- рит пользователю что делать, если нет. Она использует значения из вышеприведенной таблицы. Как уже отмечалось, такой подход не подходит для процедуры общего назначения, которая будет обслужи- вать множество разных принтеров, но он вполне подходит, когда Вы пишете драйвер данного печатающего устройства. Отметим, что в строке 120 вычисляется двухбайтное число, путем умножения старше- го байта на 256 и добавления к младшему байту. Для получения адреса регистра статуса ввода к значению полученного базового адреса добавляется 1. 100 '''Получаем адрес LPT1 и проверяем готов ли принтер 110 DEF SEG = &H40 'указываем на область BIOS 120 PRTRBASE = PEEK(9)+256*PEEK(8)+1 'адрес регистра статуса 130 IF INP(PRTRBASE) = 223 THEN 180 'если принтер готов 140 BEEP 'иначе звонок и проверки 150 IF INP(PRTRBASE) = 87 THEN LOCATE 1,1: PRINT"Strike the SELECT key": GOTO 150 160 IF INP(PRTRBASE) = 247 THEN LOCATE 1,1: PRINT"Turn the printer on": GOTO 160 170 IF INP(PRTRBASE) <> 223 THEN 170 'ждем инициализации 180 '''Теперь принтер on-line -- можно начинать печать 190 LPRINT Z$ Средний уровень. Для получения байта статуса из порта принтера надо использо- вать функцию 2 прерывания 17H. При входе DX содержит номер LPT (0-2 для LPT1-3). Эта функция сбрасывает три неиспользуемых бита байта и делает операцию исключающего ИЛИ над двумя другими, поэ- тому значения отличаются от приведенных выше: Значение Цепочка битов Интерпретация 144 10010000 принтер готов 24 00011000 принтер не готов 184 10111000 принтер выключен И опять необходимо помнить, что эти значения меняются от принтера к принтеру. Наиболее общую информацию "выключен или не готов" дает бит 3 статуса равный 0. Низкий уровень. Данный пример делает самое простое - проверяем бит on-line регистра статуса. Для получения байта статуса используется базо- вый адрес LPT1. ;---в сегменте MESSAGE DB 'Printer not ready - strike any key when OK$' ;---проверка связан ли принтер с машиной (on-line) MOV AX,40H ;ES указывает на область данных BIOS MOV ES,AX ; MOV DX,ES:[8] ;получаем базовый адрес INC DX ;смещение для регистра статуса IN AL,DX ;получаем байт статуса в AL TEST AL,1000B ;проверяем бит 3 JNZ GO_AHEAD ;если принтер on-line, то вперед ;---печатаем сообщение об ошибке и ждем нажатия клавиши MOV AH,9 ;функция вывода строки LEA DX,MESSAGE ;DS:DX указывают на сообщение INT 21H ;печатаем сообщение MOV AH,7 ;функция ожидания ввода INT 21H ;ожидаем нажатия клавиши (без эха) GO_AHEAD: ;продолжение программы 6.1.3 Интерпретация ошибок принтера и восстановление после них. Проверка ошибок не должна прекращаться на том, что Вы убеди- лись, что принтер связан с машиной. Ошибки принтера могут проис- ходить в любой момент печати и программа должна быть готова восс- тановить ситуацию при сбоях. Хотя на принтере могут происходить самые разнообразные ошибки, только три типа ошибок возвращают информацию о себе в компьютер. Это ошибка "отсутствия бумаги", ошибка "отсутствия связи с машиной" и общее сообщение "произошла ошибка". Как уже говорилось в [6.1.2], не все принтеры сообщают об этих ошибках одинаковым образом, но теоретически регистр ста- туса ввода использует следующие биты: бит 3 = 0 когда произошла ошибка на принтере бит 4 = 0 когда принтер не связан с машиной (off-line) бит 5 = 1 когда кончилась бумага на принтере В частности, бит 4 может не использоваться указанным образом. Регистр статуса ввода имеет адрес порта, который на 1 больше, чем базовый адрес принтера. Базовый адрес для LPT1 хранится по адресу 0040:0008, для LPT2 - по адресу 0040:000A и т.д. На низком уровне, когда программа посылает данные на принтер, то она постоянно обращается к биту 7 этого регистра, чтобы прове- рить готов ли принтер принять очередной символ. Несложно при этом проверить при этом и бит 3, чтобы узнать о произошедшей ошибке. Если происходит ошибка, индицируемая битами 4 и 5, то по крайней мере бит 3 будет равен 0. Программа должна постараться проанали- зировать ошибку, а затем может попросить пользователя исправить ситуацию. Отметим, что функцию DOS, которая выводит символы на принтер (функция номер 5 прерывания 21H - см. [6.3.1]), можно заставить непрерывно проверять принтер на ошибку таймаута пос- редством команды MODE. Перед загрузкой программы, использующей функцию 5, надо ввести команду MODE LPT1: ,,P (еще лучше помес- тить эту команду в файл AUTOEXEC.BAT, с тем чтобы она всегда выполнялась при загрузке системы). Все эти ошибки приводят к тому, что печать останавливается и должны быть предприняты какие-то действия прежде чем она будет продолжена. Слишком огорчительно для пользователя программы, если большая порция документа должна будет печататься заново при воз- никновении ошибки на принтере. Тщательное продумывание процедуры восстановления по ошибке позволит программе возобновить печать с начала той страницы, на которой произошла ошибка. Необходимо всегда запоминать указатель выводимых данных при начале печати новой страницы. При начале работы процедуры восстановления она может попросить пользователя вставить новый лист бумаги, а затем продолжить печать с начала той страницы, на которой произошла ошибка. Высокий уровень. В Бейсике распознаются два ошибочных условия для принтера. Код ошибки 24 возвращается когда был отменен выбор принтера, а код 27 - когда принтер выключен или в нем отсутствует бумага. Эти коды можно получить с помощью техники обнаружения ошибок, приведенной в [7.2.5]. К сожалению эффективно отлавливается только код 27. Чтобы зарегистрировать код 24 требуется примерно полминуты, в течение которых программа заморожена. Не слишком полезно прямо читать регистр статуса перед каждой операцией печати. Этот метод сработает перед началом печати, но ничем не поможет, если во время печати произойдет отмена выбора принтера. Приводим процеду- ру обработки ошибок принтера: 100 ON ERROR GOTO 1000 'устанавливаем обработку ошибок . . 1000 '''проверяем произошла ли ошибка на принтере 1010 IF ERR = 24 OR IF ERR = 27 THEN GOSUB 2000: RESUME . . 2000 BEEP: LOCATE 1,1: PRINT"Printer not ready" 2010 PRINT "Strike any key when ready" 2020 IF INKEY$ = "" THEN 2020 'ожидаем ввода 2030 RETURN Средний уровень. Когда функция 0 прерывания 17H выводит символ на принтер, то она возвращает байт статуса принтера в AH. Проверяйте значение этого байта после посылки каждого символа. BIOS слегка модифици- рует байт статуса. Обычно бит 0 не имеет значения, но в данном случае он устанавливается, когда происходит ошибка таймаута (принтер не связан с машиной). В следующем примере проверяются два типа ошибок: общая ошибка "принтер не готов" и ошибка "от- сутствия бумаги". В примере предполагается, что в начале каждой страницы (т.е. после каждого перевода формата) программа запоми- нает указатель на начало выводимых данных, помещая его в перемен- ную STARTING_PTR. Это позволяет программе при возникновении ошиб- ки повторить печать с начала страницы, а не с начала всего доку- мента. Конечно принтер должен быть повторно инициализирован перед повторной печатью и должны быть восстановлены все его параметры. (Данный пример просто иллюстрирует проверку ошибок - он ни в коей мере не является рабочей процедурой.) ;---в сегменте данных MESSAGE1 DB 'Printer off-line - strike any key when ready$' MESSAGE2 DB 'Printer out of paper - strike any key when ready$' ;---посылаем символ и проверяем на ошибку NEXT_CHAR: MOV AH,0 ;номер функции MOV DX,0 ;выбираем LPT1 MOV AL,[BX] ;BX указывает на данные INC BX ;увеличиваем указатель INT 17H ;посылаем символ на принтер TEST AH,00001000B ;выделяем бит 3 (флаг ошибки) JZ NEXT_CHAR ;если нет ошибки, то печатаем дальше TEST AH,00100000B ;выделяем бит 5 (отсутствие бумаги) JZ OFF_LINE ;переход если с бумагой все в порядке MOV AH,9 ;готовим печать сообщения LEA DX,MESSAGE2 ;DS:DX указывает на строку INT 21H ;выводим строку JMP SHORT RECOVER ;уходим на восстановление OFF_LINE: MOV AH,9 ;готовим печать сообщения LEA DX,MESSAGE1 ;DS:DX указывают на строку INT 21H ;выводим строку RECOVER: MOV BX,STARTING_PTR ;восстанавливаем указатель MOV AH,0 ;функция ожидания ввода INT 16H ;ждем CALL PRTR_INIT ;инициализация принтера JMP NEXT_CHAR ;начинаем печать с начала страницы 6.1.4 Переключение между двумя или несколькими принтерами. Компьютеры, оснащенные несколькими параллельными портами могут иметь одновременно подсоединенными два или более принтеров. Вывод может перенаправляться с одного принтера на другой двумя способа- ми. Один способ состоит в том, чтобы использовать только такие операторы вывода на печать, которые указывают на какой принтер надо осуществлять вывод. Вы можете написать такой код, который позволит Вам изменять спецификацию. Второй способ переключения принтеров состоит в использовании вывода по умолчанию на LPT1, но указания другого принтера, кото- рый будет использоваться в качестве LPT1. Это достигается измене- нием базового адреса, относящегося к LPT1. Этот базовый адрес хранится в области данных BIOS в ячейке 0040:0008. Поменяйте его с базовым адресом для LPT2 или 3 (хранящимися в ячейках 0040:000A и 0040:000C) и в качестве LPT1 будет использоваться другой адап- тер. Высокий уровень. В Бейсике, если принтер был открыт оператором OPEN "LPT1" AS #1, то чтобы переключиться на другой принтер надо сначала напи- сать оператор CLOSE #1, а затем открыть другой принтер с помощью оператора OPEN "LPT2" AS #1. Впоследствии все операторы PRINT #1 будут направлять свой вывод на второй принтер. Это изменение труднее осуществить в программах, использующих оператор LPRINT, поскольку LPRINT по умолчанию посылает весь вывод на LPT1. В этом случае Вам необходимо поменять базовые адреса принтеров. Следую- щая программа на Бейсике делает именно это, переключая LPT1 и LPT2. Ее повторное использование переключает адреса обратно, возвращая систему к первоначальной конфигурации. 100 DEF SEG = &H40 'указываем на область данных BIOS 110 X = PEEK(8) 'получаем младший байт адреса LPT1 120 Y = PEEK(9) 'получаем старший байт адреса LPT1 130 POKE 8,PEEK(10) 'переносим младший байт адреса LPT2 140 POKE 9,PEEK(11) 'переносим старший байт адреса LPT2 150 POKE 10,X 'посылаем младший байт LPT1 в LPT2 160 POKE 11,Y 'посылаем старший байт LPT1 в LPT2 170 SYSTEM 'выходим из Бейсика Эта программа будет очень кстати, если готовое программное обеспечение не адресуется к нужному принтеру. Ее можно откомпили- ровать и хранить на диске, скажем под именем OTHERPRN, после чего надо будет только напечатать ее имя (в ответ на запрос DOS), чтобы переключиться с принтера на принтер. Если у Вас нет транс- лятора с Бейсика, то создайте командный файл OTHERPRN.BAT и по- местите в него строку BASIC OTHERPRN. Когда Вы напечатаете OT- HERPRN, то будет автоматически загружен Бейсик, который загрузит и выполнит программу OTHERPRN.BAS, после чего Вы вернетесь в операционную систему. Необходимо, правда, чтобы на диске имелся интерпретатор Бейсика BASIC.COM. Помните, что Вы должны устоять перед искушением испытать эту программу перед тем, как она будет записана на диск, поскольку если Вы ее запустите, то она сотрет себя. Низкий уровень. Один способ, которым программа на ассемблере может изменить принтер, на который она посылает данные, состоит в использовании для печати только функции 0 прерывания 17H [6.3.1]. Эта функция требует, чтобы номер принтера был помещен в DX. Заведите перемен- ную для этого номера, с тем чтобы он мог быть изменен в любой момент. Вторая возможность состоит в обмене базовых адресов LPT1 и LPT2 или LPT3. Следующая программа делает именно это. Как и все короткие утилиты, она должна писаться в COM форме, как объяснено в [1.3.6]. ;---обмен базовыми адресами LPT1 и LPT2 MOV AX,40H ;сегмент области данных BIOS MOV ES,AX ;ES указывает на данные MOV BX,8 ;смещение для базового адреса LPT1 MOV DX,ES:[BX] ;сохраняем базовый адрес LPT1 MOV AX,ES:[BX]+2 ;сохраняем базовый адрес LPT2 MOV ES:[BX],AX ;меняем базовый адрес LPT2 MOV ES:[BX]+2,DX ;меняем базовый адрес LPT1 Раздел 2. Установка спецификаций печати. Для установки различных спецификаций, относящихся к формату страницы, стилю шрифта и т.п., на принтер посылаются специальные управляющие коды. Эти коды посылаются на принтер как и любые другие данные. Некоторые из них это простые однобайтные коды из числа первых 32-х набора кодов ASCII. Эти управляющие коды (пере- численные в [7.1.9]) инициируют такие простые действия принтера, как перевод строки или перевод формата (прогон страницы). Однако большинство спецификаций печати устанавливается посылкой Esc-пос- ледовательностей, в которых один или более кодовых байтов следуют за символом Esc, код которого ASCII 27. Начальный код Esc инфор- мирует принтер, что символ(ы) который следует за ним следует интерпретировать как команду, а не как данные. Такие Esc-последо- вательности обычно не имеют символа-ограничителя, поскольку прин- тер "знает" длину каждой последовательности. Только в некоторых случаях, когда последовательность может иметь разную длину, тре- буется ограничивающий символ, в качестве которого всегда исполь- зуется код ASCII 0. Почти во всех случаях спецификации установленные этими кодами действуют до тех пор, пока они не будут явно отменены. Как только будет получен код, например, подчеркивания, то оно будет осу- ществляться до тех пор, пока не будет послан код отмены подчерки- вания. Буфер принтера может быть очищен без отмены установленных спецификаций. Но если произошла ошибка на принтере и принтер был выключен и включен, то необходимо снова устанавливать все специ- фикации. Большинство кодов устанавливающих спецификации принтера пере- мешаны с данными, на которые они действуют. Например, данные для слова, которое должно быть выделено жирным шрифтом, должны пред- варяться Esc-последовательностью, включающей жирный шрифт, и завершаться Esc-последовательностью, выключающей его. Поскольку универсальный стандарт на эти коды отсутствует, то печать с ис- пользованием мощных возможностей требует, чтобы для каждого под- держиваемого принтера были написаны драйверы. Каждый драйвер преобразует инструкции, генерируеиые процедурой печати, в прото- кол, используемый данным принтером. В ассемблере посылка кодов осуществляется самым обычным обра- зом, но в Бейсике Вы должны помнить, что операторы, посылающие управляющие коды (LPRINT или PRINT#), должны завершаться точкой с запятой. В противном случае операторы будут автоматически добав- лять к посылаемым кодам пару возврат каретки/перевод строки. Обсуждения и примеры последующих страниц в основном относятся к графическому принтеру IBM. Коды, используемые этим принтером, настолько же "стандартны", насколько и любой другой протокол. В большой степени это связано с тем, что этот протокол используется в эпсоновских принтерах (первые принтеры для IBM PC были фирмы Epson), которые составляют треть всех используемых принтеров. Управляющие коды, используемые принтерами IBM сравниваются в разделе [6.2.7]. Хотя информация, приведенная в данном разделе, может быть неприменима к тому принтеру, с которым Вы работаете, но большинство общих принципов применимо. 6.2.1 Установка текстового и графического режимов. Принтер всегда находится в текстовом режиме, до тех пор пока он специально не переведен в графический режим. Команда, устанав- ливающая графический режим, должна сообщать какое число байтов графических данных будет передано (но не больше одной строки) и после того, как это число байтов будет интерпретировано как гра- фическое изображение, принтер вернется в текстовый режим. По этой причине нет команды, которая переводит принтер в текстовый режим. Число графических режимов у разных принтеров разное. Во всех случаях, за кодом устанавливающим графический режим следуют 2 байта, указывающие какое число графических байтов будет передано (сначала младший байт). Чтобы вычислить значение этих двух бай- тов, разделите число байтов данных на 256 и поместите результат во второй байт, а остаток - в первый байт. За этими двумя байтами должны сразу следовать байты данных. Каждый байт определяет цепочку битов, соответствующих восьми вертикальным точкам одной позиции в строке. Младший бит (1) соот- ветствует низу колонки, а старший бит (128) - верху. Например, чтобы напечатать пирамиду, пошлите сначала байт, у которого уста- новлен только нижний бит, затем байт у которого установлены 2 нижних бита и т.д. После восьмого байта расположите те же байты в обратном порядке. Значение первого байта будет 1, второго - 3 (1+2), затем 7 (1+2+4), затем 15 (1+2+4+8) и т.д. На рисунке 6-1 изображена вся картина. Для печати пирамиды в Бейсике на графическом принтере IBM напишите следующий код: 100 LPRINT CHR$(27);CHR$(75);CHR$(15);CHR$(0);CHR$(1);CHR$(3); CHR$(7);CHR$(15);CHR$(31);CHR$(63);CHR$(127);CHR$(255); CHR$(127);CHR$(63);CHR$(31);CHR$(15);CHR$(3);CHR$(1); Первые два байта переводят принтер в графический режим с 480 точками, следующие два - сообщают, что будет передано 15 байтов графических данных, а затем идет последовательность байтов дан- ных. Конечно то же самое можно запрограммировать умнее, организо- вав цикл, в котором будут передаваться байты данных. Отметим, что все проблемы в этом случае возникают, если указанное число байтов не соответствует числу посылаемых байтов. Чтобы создать пробел между графическими фигурами выведите несколько байтов с нулевым значением. В Бейсике, когда в одной строке выводится больше 80 байтов графических данных, не забудьте предварительно установить "бесконечную" ширину принтера. Для этого надо ввести команду WIDTH "LPT1:",255. Графический принтер IBM имеет четырек графических режима, которые более или менее "стандартны". Они такие: 27,75 480 точек в строке. Нормальный режим. Максимум 480 байтов данных на оператор. 27,76 960 точек в строке. Удвоенное горизонтальное разрешение, но печать вдвое медленнее (двойная плотность). Максимум 960 байтов данных на оператор. 27,89 960 точек в строке, печать с нормальной скоростью (двой- ная плотность с высокой скоростью). Две точки, прилегаю- щие по горизонтали, не могут быть напечатаны, поскольку не будут успевать иголки печатающей головки. Если делается попытка их напечатать, то вторая точка будет игнорировать- ся. Максимум 960 байтов данных на оператор. 27,90 1920 точек в строке, печать вдвое медленнее (четверная плотность). Соседние точки по горизонтали должны отстоять по крайней мере на 3 точки (т.е. 1 печатаем, 2 пропуска- ем). Максимум 1920 байтов данных на оператор. В более плотных режимах две прилегающие по горизонтали точки не могут быть напечатаны. Чтобы заполнить пропуски между точками, верните каретку к левому полю, немного сдвиньте печатающую голов- ку вправо и сделайте второй проход, используя те же данные. Вот сравнение плотностей печати вызываемых одними и теми же управляю- щими кодами на разных принтерах: Коды Графический Цветной Компактный Пропринтер 27,75 480 точек 1108 560 480 27,76 960 точек 2216 - 960 27,89 960 точек 2216 - 960 27,90 1920 точек 4432 - 1920 Цветной принтер уникален из принтеров IBM тем, что он может устанавливать масштабный коэффициент (aspect ratio) для графичес- ких изображений. Этот коэффициент отражает разницу горизонтальных и вертикальных расстояний между точками. Обычно желателен коэффи- циент 1:1, поскольку в противном случае трудно проводить графи- ческие вычисления. Но при копировании графического экрана надо чтобы масштабный коэффициент был таким же, как у дисплея. В эк- ранном режиме умеренного разрешения 5 точек по вертикали занимают тот же размер, что 6 точек по горизонтали. Это соответствует масштабному коэффициенту 5:6 и именно это значение используется по умолчанию цветным принтером. Допускаются только коэффициенты 1:1 и 5:6. 6.2.2 Управление расстоянием между строками. Если не принимать во внимание принтеры, имеющие специальные возможности графопостроителя, то вся печать осуществляется стро- ками. Даже графические изображения рисуются построчно, хотя в этом случае нет пустых мест между строками. Код ASCII 10 - стан- дартный управляющий код перевода строки. Посылка его на принтер (без предшествующего кода Esc) приводит к тому, что бумага будет продвинута вперед на указанный интервал. Обычно, если перевод строки не посылается за кодом возврата каретки, то печатающая головка возвращается к левому краю бумаги и можно снова печатать на той же строке. Однако можно сделать так, чтобы перевод строки делался автоматически при каждом возврате каретки. Этим управляют переключатели на принтере. Это же можно сделать установив бит 1 регистра управления выводом (см. [6.1.0]). Многие принтеры могут включать и выключать автоматический перевод строки с помощью управляющих кодов 27,53, а некоторые могут делать обратный пере- вод строки с пмощью кодов 27,93. По умолчанию графический принтер использует интервал печати равный 1/6 дюйма (т.е. выводят 6 строк на дюйм) и к этому режиму всегда можно вернуться, посылая управляющие коды 27,50 (эти коды используются также в сочетании с коды изменения интервала между строками, обсуждаемыми ниже). Для этого принтера имеются еще два предопределенных межстрочных интервала, 1/8 дюйма и 7/72 дюйма. Соответствующие им управляющие коды 27,48 и 27,49. Возможна и более тонкая градация межстрочных интервалов. Гра- фический принтер использует три кода, позволяющие изменить интер- вал на очень малую величину. Все три управляющих кода используют 2-хбайтную Esc-последовательность, за которой следует число 72-х или 216-х долей дюйма, определяющих межстрочный интервал. Верти- кальное расстояние между центрами двух точек равно 1/72 дюйма. Интервал 8/72 дюйма не оставляет промежутка между строками (9 строк на дюйм). Стандартный интервал 6 строк на дюйм задается числом 12/72 дюйма. Наконец, 1/216 равна 1/3 от 1/72. Изменение на такую величину позволяет печатающей головке слегка сдвинуться от центра строки, с тем чтобы точки при втором проходе заполнили промежутки, обеспечивая печать более высокого качества. Вот эти Esc-последовательности: Изменение Esc-последовательность 72-е дюйма 27,65,n (где n от 1 до 85) 216-е дюйма 27,51,n (где n от 1 до 255) 216-е дюйма 27,74,n (где n от 1 до 255) Команды для изменения интервала в 72-х дюйма не станут активными до тех пор, пока не встретится второй управляющий код: 27,50. Как объяснялось выше, этот код может также использоваться отдельно для восстановления стандартного интервала в 1/6 дюйма. Если ранее была использована команда 27,65,n, то для восстановления интерва- ла в 1/6 дюйма надо послать команду 27,65,12,27,50. Два управляю- щих кода для интервалов в 1/216 дюйма не идентичны. Первый код устанавливает, что все последующие переводы строки будут выпол- няться с указанным интервалом; второй же действует только на один перевод строки, а затем возвращает интервал, который действовал до этого. Следующая таблица сравнивает межстрочные интервалы, вызываемые одними и теми же управляющими кодами на различных принтерах IBM: Коды Матричный Графический Цветной Компактный Струйный Ромашка Про- принтер принтер принтер принтер принтер принтер 27,48 1/8 1/8 1/8 1/9 1/8 1/8 1/8 27,49 7/72 7/72 6/72 1/9 9/96 7/72 27,50 1/6 1/6 1/6 1/6 1/6 1/6 1/6 27,51 n/216 n/144 n/216 27,65 n/72 n/72 n/72 n/72 n/72 27,74 n/216 n/144 n/216 Независимо от того как изменяются межстрочные интервалы, прин- тер всегда контролирует прямые и обратные движения листа, поэтому пропуски перфорации всегда делаются вовремя. 6.2.3 Управление движением бумаги. Бумага на принтере передвигается командами перевода строки, вертикальной табуляции и перевода формата. Установкой переключа- телей на принтере определяется будет ли принтер автоматически переходить на новую страницу при обнаружении перфорации между страницами. Если перфорация не будет пропускаться, то печать может завершиться прямо на вернем краю очередной страницы. Про- пуск перфорации оставляет по три пустых строки сверху и снизу каждой страницы. На самом деле принтер не распознает перфорацию, вместо этого он считает, что в начальный момент бумага выравнена на начало страницы и считает число переводов строки. Можно прог- раммно переопределить установку переключателей, посылая на прин- тер управляющие коды 27,56, чтобы принтер не делал пропуска пер- форации и 27,57, чтобы делал пропуск перфорации. Графический принтер использует код, который определяют число строк, пропускаемых между страницами. Этот код 27,78,n, где n - число строк от 1 до 127. Например, код 27,78,10 приведет к тому, что принтер будет пропускать по 10 строк. Если межстрочный интер- вал равен 1/6 дюйма, то 11-тидюймовая страница будет содержать 66 строк и после печати каждых 56-ти строк принтер будет делать пропуск 10-ти строк. Уже Ваша программа должна позаботиться, чтобы в самом начале прогнать бумагу на 5 строк, с тем чтобы 55 строк текста были центрированы на каждой странице. Если используется бумага, размер которой отличается от стан- дартного 11-тидюймового, то можно изменить длину страницы, с тем чтобы пропуски перфорации происходили в нужном месте и чтобы перевод формата устанавливал бумагу в правильную позицию. Размер страницы может устанавливаться либо числом строк на странице, либо размером в дюймах. Чтобы установить число строк на странице, пошлите код 27,67,n, где n - число строк. Та же последователь- ность используется и для установки длины страницы в дюймах, за исключением того, что длина страницы записывается в форме 0,n, где n может быть от 1 до 22 дюймов. Для стандартной страницы надо послать команду 27,67,0,11. 6.2.4 Управление положением печатающей головки. Печатаемый текст распределяется по странице частично за счет движения бумаги [6.2.3], а частично за счет движения печатающей головки. Головка может быть позиционирована в любое место, но не путем задания ее координат. Вместо этого указывается ее смещение, относительно самой левой позиции, которую она может достигать. У принтера нет датчиков, сообщающих текущее положение головки. Ваша программа должна отслеживать положение головки, если оно должно быть известным. При этом хорошей практикокй является начинать печать с подачи управляющего кода 27,60, который сдвигает головку в самую левую позицию, не делая перевода строки (то же самое делает и код возврата каретки). При печати текста имеется несколько способов передвинуть го- ловку в нужное положение. Она может сдвигаться вправо подачей одного или нескольких символво пробела или табуляции и влево подачей одного или нескольких символов "возврат на шаг" или сим- вола возврата каретки. Движения осуществляются непрерывно - не воспринимайте их как соответствующие последовательности на обыч- ной пишущей машинке. До тех пор, пока Ваша программа знает на- чальное положение печатающей головки она может комбинацией пере- водов строки, пробелов, табуляций и возвратов на шаг форматиро- вать Ваш вывод в соответствии с Вашими пожеланиями. Принтеры, которые умеют выполнять обратный пеервод строки могут использо- ваться и как графопостроители. В графических режимах возможно перемещение головки на малые доли дюйма. При печати текста Вы можете войти в графический ре- жим, чтобы добиться разных промежутков между словами. К сожале- нию, этот процесс существенно замедляет печать. Смотрите пример в пункте [6.3.2]. Имеется специальный код, который заставляет головку всегда возвращаться в крайнюю левую позицию перед печатью очередной строки, отменяя двунаправленную печать. Хотя это значительно замедляет печать, однако при этом достигается более точное пози- ционирование головки. Это особенно полезно при работе в графичес- ком режиме. Чтобы включить однонаправленную печать надо послать код 27,85,1, а чтобы вернуться к двунаправленной печати - код 27,85,0. 6.2.5 Установка позиций табуляции. В зависимости от принтера могут устанавливаться позиции гори- зонтальной и вертикальной табуляции (графический принтер IBM не имеет вертикальной табуляции). Горизонтальные табуляции опреде- ляются, как смещения от левого края, выраженные в пробелах. В некоторых случаях допускаются до 112 позиций горизонтальной табу- ляции. Аналогично, вертикальные табуляции определяются как смеще- ния относительно верха страницы, а измеряются они в межстрочных интервалах. Для большинства принтеров IBM допускается не больше 64-х позиций вертикальных табуляций. Первые два байта кода для установки горизонтальной табуляции 27,68, а для установки вертикальной табуляции - 27,66. Для обоих типов табуляций далее идет строка байтов, дающая позиции табуля- ции в возрастающем порядке. Эта строка должна завершаться байтом ASCII 0, который служит ограничителем. Для установки горизонталь- ной табуляции в позициях 15, 30 и 60 пошлите на принтер код 27, 68, 15, 30, 60, 0. Для установки вертикальной табуляции в строках 8 и 12 - пошлите код 27, 66, 8, 12, 0. Отметим, что если размер страницы отличается от стандартных 11-ти дюймов, то он должен быть установлен перед установкой позиций вертикальной табуляции. Вертикальная табуляция отменяется кодом 27,67. Отметим, что большинство принтеров не имеют установки полей как таковой. Левое поле может создаваться за счет вывода табуля- ции или ряда пробелов в начале каждой строки. Для точной установ- ки полей перейдите в графический режим и выведите ряд байтов ASCII 0. Правое поле создается просто за счет ограничения длины строки. 6.2.6 Изменение шрифта печати. Ширина страницы 8 1/2 дюйма позволяет напечатать в строке до 80-ти обычных символов, если все они имеют одинаковую ширину. Пропорциональная печать [6.3.3] позволяет поместить в строке еще несколько символов. С другой стороны, плотная печать позволяет вывести в строке 132 символа, печать с двойной шириной - 40 сим- волов, а плотная печать с двойной шириной - 64 символа. Имейте ввиду, что использование печати с разной шириной в одной строке приведет к трудностям с форматированием. Большинство матричных принтеров предоставляют набор режимов печати специальными шрифтами. Вот перечень стандартных возможнос- тей предоставляемых графическим принтером IBM: Плотная печать: Для включения режима плотной печати надо послать однобайтный управляющий код 15. Для выключения этого режима - код 18. Стан- дартная страница шириной 8 1/2 дюйма позволяет напечатать 132 символа в строке в этом режиме. Печать с двойной шириной: Для того, чтобы принтер начал печатать с двойной шириной надо послать на него управляющий код 14. Режим печати с двойной шири- ной необычен тем, что принтер автоматически выключает этот режим, когда встречает символ возврата каретки или перевода строки. Поскольку такой вид печати обычно используется для однострочных заголовков, то это свойство удобно. Чтобы выключить этот режим в середине строки пошлите код 20. Выделенная печать: При выделенной печати каждый символ печатается два раза в одной и той же позиции. Это делает точки темнее, что создает эффект выделения. Скорость печати при этом уменьшается вдвое. Для включения этого режима пошлите код 27,69. Для выключения - 27,70. Печать за два прохода: В режиме печати за два прохода бумага сдвигается на 1/216 дюйма перед вторым проходом печатающей головки. При этом полу- чаются более заполненные буквы, которые к тому же выглядят ярче. Скорость печати уменьшается вдвое. Этот режим включается управ- ляющим кодом 27,71, а выключается кодом 27,72. Печать с подчеркиванием: Печать с подчеркиванием может выполняться двумя способами. Графический принтер имеет режим подчеркивания, в котором подчерк печатается под каждым символом, включая пробелы. Для графического принтера IBM этот режим включается кодом 27,45,1, а выключается кодом 27,45,0. Принтеры, не имеющие режима подчеркивания могут сделать подчерки при втором проходе по той же строке, печатая символы подчеркивания (ASCII 95) в тех местах, где оно нужно и пробелы (ASCII 32) во всех остальных позцициях. Второй проход достигается тем, что после первого прохода подается код возврата каретки без кода перевода строки. Второй проход не мешает принте- ру правильно подсчитывать строки при вычислении размера страницы. Печать с верхними и нижними индексами: На графических принтерах текст с верхними и нижними индексами сжимается вертикально. Для печати верхнего индекса пошлите управ- ляющий код 27,83,0, а для печати нижнего - 27,83,1. Можно прямо переходить от одних индексов к другим. Для выключения печати индексов, с тем, чтобы принтер оказался на текущей строке пошлите управляющий код 27,84. Некоторые режимы не могут использоваться в комбинации с други- ми. Если Вы хотите использовать 4 режима одновременно, то прокон- сультируйтесь со следующей таблицей. В каждом из шести столбцов приведена допустимая комбинация. Комбинация 1 2 3 4 5 6 нормальный Х Х сжатый Х Х выделенный Х Х за два прохода Х Х Х с индексами Х Х Х двойной ширины Х Х Х Х Х Х с подчеркиванием Х Х Х Х Х Х 6.2.7 Сравнение возможностей принтеров IBM. В следующей таблице сравниваются управляющие коды для принте- ров IBM. Не вся информация относительно кодов точна (обращайтесь к документации IBM), а в ряде случаев уникальные коды опущены. Целью настоящей таблицы является показ диапазона возможностей принтеров и указание тех кодов, которые можно считать стандартны- мия. Отметим, что коды для первых четырех принтеров приведены в выпуске "Возможности и адаптеры" (Options and Adapters) из серии технических руководств, а коды для остальных принтеров приведены в сопровождающих их руководств по эксплуатации. Код Функция Матричный Графический Цветной Компактный Струйный Ромашка Пропринтер принтер принтер принтер принтер принтер Перемещение бумаги: 10 перевод строки Х Х Х Х Х Х Х 11 вертикальная табуляция Х Х Х Х Х Х 12 перевод формата Х Х Х Х Х Х Х 13 возврат каретки Х Х Х Х Х Х Х 27,52 установка начала страницы Х Х Х 27,56 игнорировать отсутствие бумаги Х Х 27,57 отмена игнор. отсутствия бумаги Х Х 27,66 установка вертикальных таб-ций Х Х Х Х Х 27,66 очистка вертикальных таб-ций Х 27,88 установка пропуска перфорации Х Х Х Х Х 27,79 отмена пропуска перфорации Х Х Х Х Х Перемещение печатающей головки: 8 возврат на шаг Х Х Х Х 9 горизонтальная табуляция Х Х Х Х Х Х Х 27,60 сдвиг головки в левый конец Х Х Х 27,62 установка индекса горизонталь- Х ного движения 27,68 установка горизонт. таб-ции Х Х Х Х Х Х Х 27,68 очистка горизонт. таб-ции Х 27,77 автоматическое форматирование Х 27,80 вкл./выкл. пропорц. печати Х Х 27,82 восстан. таб-ций по умолчанию Х Х Х Х 27,85 вкл./выкл. однонапр. печати Х Х 27,88 установка левого/правого поля Х Х 27,100 программируемый пробел Х 27,101 программируемый возврат на шаг Х Межстрочные и межсимвольные интервалы: 27,48 межстрочный интервал 1/8 дюйма Х Х Х Х Х Х 27,48 межстрочный интервал 1/9 дюйма Х 27,48 межстрочный интервал 7/72 дюйма Х Х 27,49 межстрочный интервал 7/72 дюйма Х 27,49 межстрочный интервал 9/96 дюйма Х 27,49 межстрочный интервал 6/72 дюйма Х 27,49 межстрочный интервал 1/9 дюйма Х 27,50 начать программируемый пере- Х Х Х вод строки по 27,65 27,50 межстрочный интервал 1/6 дюйма Х Х Х Х Х Х Х 27,51 программируемый перевод Х Х строки (n/216) 27,51 программируемый перевод Х строки (n/144) 27,53 вкл./выкл. автоматич. пере- Х Х Х Х Х вода строки 27,65 программируемый перевод Х Х Х Х Х строки (n/72) 27,67 установка длины страницы Х Х Х Х Х Х Х 27,74 программируемый перевод Х Х строки (n/216) 27,74 программируемый перевод Х строки (n/144) 27,93 обратный перевод строки Х 27,104 перевод на пол-строки вперед Х 27,105 перевод на пол-строки назад Х Управление шрифтами: 11 режим 15 символов на дюйм Х 14 включение режима двойной ширины Х Х Х Х Х Х 15 включение плотной печати Х Х Х Х Х Х 18 выключение плотной печати Х Х Х Х Х 18 режим 10 символов на дюйм Х Х Х 20 выключ. режима двойной ширины Х Х Х Х Х Х 27,45 вкл./выкл. подчеркивания Х Х Х Х Х Х 27,58 режим 12 символов на дюйм Х Х Х 27,69 включение жирной печати Х Х Х Х 27,70 выключение жирной печати Х Х Х Х 27,71 включение печати в 2 прохода Х Х Х Х Х 27,72 выключение печати в 2 прохода Х Х Х Х Х 27,83 включение печати индексов Х Х Х Х Х 27,84 выключение печати индексов Х Х Х Х Х 27,87 вкл./выкл. печати двойной Х Х Х Х Х с шириной 27,91 включение цветного подчеркив. Х 27,95 вкл./выкл. overscore Х Установка специальных шрифтов и цветов: 27,54 выбор набора символов 2 Х Х Х Х Х 27,55 выбор набора символов 1 Х Х Х Х Х 27,61 загрузка шрифта Х Х 27,73 изменение качества печати Х Х Х 27,92 печатать управляющие символы Х Х Х 27,94 печатать все символы Х Х Х 27,97 сдвиг ленты в конце страницы Х 27,98 выбор 4-й полосы ленты Х 27,99 выбор 3-й полосы ленты Х 27,109 выбор 2-й полосы ленты Х 27,121 выбор 1-й полосы ленты Х Графические режимы: 27,75 установка режима 480 точек Х Х 27,75 установка режима 560 точек Х 27,75 установка режима 1108 точек Х 27,76 установка режима 960 точек Х Х 27,76 установка режима 2216 точек Х 27,89 установка режима 960 точек Х Х с нормальной скоростью 27,89 установка режима 2216 точек Х 27,90 установка режима 1920 точек Х Х 27,90 установка режима 4432 точек Х 27,91 установка разрешения/цвета Х 27,110 установка масштабного коэф-нта Х Х Другие возможности: 7 звонок Х Х Х Х Х 20 выключ. режима двойной ширины Х Х Х Х Х Х 17 выбор принтера Х Х Х Х Х 19 отмена выбора принтера Х Х Х Х 24 очистка буфера Х Х Х Х Х Х Х 27,81 отмена выбора указанного Х Х принтера Раздел 3. Посылка данных на принтер. Посылка данных на принтер тривиальна в языках высокого уровня, а для программиста на языке ассемблера имеется ряд функций опера- ционной системы, которые делают задачу также достаточно простой. Программирование на низком уровне требует больше работы, но зато предоставляет больше возможностей. Как правило, процедуры печати низкого уровня посылают символ на принтер, а затем постоянно проверяет регистр статуса ввода порта, к которому присоединен принтер. Следующий символ посылается только тогда, когда принтер сигнализирует, что он готов (принтер может не печатать символ сразу, а запасать его в своем буфере, до тех пор пока не будет получена целая строка символов для печати). Кроме того, процедуры низкого уровня могут использовать преры- вание принтера или могут имитировать действие этого прерывания. С помощью специального программирования можно сделать так, что принтер будет делать прерывание проценссора, когда он готов к приему следующего символа. Процедура обработки прерывания посы- лает следующий символ, после чего процессор может продолжать заниматься своими делами. Этот метод используется для фоновой печати (которую называют также спулингом). Поскольку физические перемещения деталей принтера намного медленнее, чем скорость электроники компьютера, то вывод символов на принтер занимает лишь малую долю процессорного времени. Использование прерывания позволяет использовать это время эффективно. При посылке данных на принтер требуется сравнительно небольшие усилия, чтобы добиться ужасно сложного вывода. Все сложные кар- тинки, которые может выводить принтер, достигаются за счет комби- нирования текстовых и графических данных, а также многочисленных кодов управления принтером, обсуждавшихся ранее в этой главе. Комбинируя в одной строке текстовый и графический режимы, можно добиться выравнивания правого поля и пропорциональной печати. Кроме того любой графический принтер может создавать специальные символы произвольного вида, а за счет аккуратного манипулирования надпечатки и межстрочного интервала могут выводиться любые симво- лы псевдографики. 6.3.1 Вывод текстовых или графических данных на принтер. Процессор может заниматься только посылкой данных на принтер или он может печатать в фоновом режиме, за счет использования прерывания принтера. Возможна и третья альтернатива, когда прог- рамма посылает символы на принтер через определенные интервалы, что можно рассматривать как "псевдопрерывание". Этот метод не так тесно координируется с работой принтера, как настоящее прерыва- ние, но во всяком случае работа принтера не критична ко времени. Независимо от того как выводятся данные, каждый раз на принтер посылается только 1 байт данных. Языки высокого уровня предостав- ляют функции, которые вроде бы выводят сразу целые строки, однако на самом деле эти функции разбивают строки на отдельные символы. Обычно языки высокого уровня посылают на принтер пару возврат каретки/перевод строки в конце каждой строки. С другой стороны, программы на ассемблере должны сами добавлять эту пару кодов. Из-за этого приходится немного больше программировать, но взамен Вы получаете намного большую гибкость, особенно в отношении про- верки ошибок. Высокий уровень. Для посылки данных на принтер Бейсик предоставляет операторы LPRINT и PRINT#. LPRINT не требует никакой подготовки, но для вывода оператором PRINT# Вы должны предварительно открыть принтер в точности так же как и файл, с помощью оператора OPEN "LPT1" AS #1 или OPEN "LPT3" AS #2. Оператор LPRINT всегда адресуется к LPT1, в то время как PRINT# может адресоваться к любому принтеру. Пара возврат каретки/перевод строки автоматически добавляется в конце любого оператора LPRINT или PRINT#, если только он не завершается точкой с запятой. Для избежания ненужных переводов строки не забывайте завершать посылки любых управляющих кодов точкой с запятой. То же самое надо делать, если Вы хотите, чтобы строки текста печатались подряд, прилегающие одна к другой. Одна- ко имейте ввиду, что многие принтеры не начинают печатать до тех пор, пока они не получат данные для целой строки. Это определяет- ся либо символом возврат каретки, либо тем, что число переданных символов достигло 80-ти (или другого числа). Не забывайте послать завершающий код возврата каретки, чтобы вытолкнуть последнюю порцию символов из буфера принтера. Принтер автоматически переходит на слдеующую строку по дости- жению конца строки. По умолчанию размер строки принтера равен 80 символам, но у широких принтеров это значение может быть больше. Строки, выводимые в режимах плотной печати или печати двойной ширины, также меняют длину строки. Для изменения номера столбца, по достижении которого головка принтера перейдет на следующую строку, можно установить ширину принтера командой WIDTH "LPT1",n - где n требуемый номер столбца. Когда печатается строка, длина которой больше или равна ширине принтера, то печатающая головка переходит на следующую строку, что эквивалентно выполнению кодов возврат каретки/перевод строки. Это означает, что в случае, когда длина строки в точности равна ширине принтера, то будет сделано два перевода строки, если эта строка завершается, как обычно, парой возврат каретки/перевод строки. При графической печати принтер обычно устанавливают на беско- нечную ширину. Чтобы сделать это, надо подать команду установки ширины, равной 255, WIDTH "LPT1",255. Если Вы забудете включить эту команду, то при выводе длинных последовательностей графичес- ких данных Бейсик будет вставлять пару возврат каретки/перевод строки после каждых 80 байтов данных. Эти добавочные символы будут включаться в общее число байтов данных для графической печати, поэтому конец передаваемых данных будет просто опущен. Один оператор LPRINT может содержать несколько элементов дан- ных в различных видах. Информация может содержаться в самом опе- раторе, как например в LPRINT "The rain in Spain", или на нее можно ссылаться по имени переменной, как в случае X$ = "The rain in Spain": LPRINT X$. Специальные символы могут включаться за счет использования функции CHR$. Управляющие коды обычно посы- лаются именно этим способом, например, LPRINT CHR$(10) посылает на принтер управляющий код перевода строки. Чаще всего CHR$ ис- пользуется при посылке кодов ASCII, которые нельзя ввести с кла- виатуры. Любые из перечисленных типов данных могут быть объедине- ны в одном операторе. Если Вы хотите, чтобы различные элементы данных печатались подряд, то разделяйте их точкой с запятой; если же Вы разделите их запятыми, то следующий элемент будет выводить- ся со следующей позиции табуляции. Это говорит о том, что опера- тор LPRINT форматирует печать в точности так же, как это делает оператор PRINT при выводе на экран. Вот несколько примеров: 100 LPRINT S$;" and ";Y$ 'комбинация трех строк 110 LPRINT X, Y, Z 'вывод трех чисел 120 LPRINT "The total is "; X 'комбинация строки и числа 130 LPRINT "The ";CHR$(27);CHR$(45);CHR$(1);"real"; CHR$(27);CHR$(45);CHR$(0);" thing." 'подчеркивание среднего слова Оператор PRINT# может использовать те же типы данных, что и оператор LPRINT, и он также позволяет включать несколько элемен- тов данных в один оператор и смешивать различные типы данных. Точки с запятой и запятые действуют в нем аналогичным образом. Вот примеры, эквивалентные вышеприведенным: 100 OPEN "LPT1:" AS #2 110 PRINT #2,S$;" and ";Y$ 120 PRINT #2,X, Y, Z 130 PRINT #2,"The total is "; X 140 PRINT #2,"The ";CHR$(27);CHR$(45);CHR$(1);"real"; CHR$(27);CHR$(45);CHR$(0);" thing." Средний уровень. Функция 0 прерывания 17H посылает один символ на принтер. Поместите символ в AL, а номер принтера в DX. При возврате AH будет содержать регистр статуса, который надо постоянно проверять для обнаружения ошибок. В [6.1.3] объясняется как это делать. Для вывода потока данных установите указатель на буфер, содержащий данные, и напишите процедуру типа следующей: ;---вывод данных на LPT1 MOV CX,NUMBER_CHARS ;CX содержит число байт для вывода MOV DX,0 ;выбираем LPT1 NEXT_CHAR: MOV AH,0 ;функция посылки символа на принтер MOV AL,[BX] ;BX указывает на буфер данных INT 17H ;посылаем символ TEST AH,8 ;проверяем бит ошибки JNZ PRNTR_ERROR ;на обработку ошибки INC BX ;увеличиваем указатель LOOP NEXT_CHAR ;выводим следующий символ Стандартное прерывание MS DOS для вывода на принтер это функ- ция 5 прерывания 21H. Просто поместите символ в DL и выполните прерывание. Эта функция всегда выводит на LPT1 и у нее нет возв- ращаемых регистров. ;---вывод данных на LPT1 MOV AH,5 ;номер функции MOV DL,CHAR ;готовим печатаемый символ INT 21H ;посылаем его на принтер Другой способ вывода данных на принтер это функция 40H преры- вания 21H. Это функция стандартного вывода, с использованием метода дескриптора файлов для доступа к файлу или устройству [5.3.0]. В данном случае эта функция использует специальный пре- допределенный номер файла для принтера. Этот номер #4 и его надо поместить в BX. Функция имеет доступ только к LPT1, поэтому для вывода на другой принтер Вам надо поменять базовые адреса [6.1.4]. DS:DX должны указывать на выводимые данные, а CX содер- жать число посылаемых байтов. Например: ;---вывод 120 байтов данных на LPT1 MOV AH,40H ;номер функции MOV BX,4 ;номер файла для принтера MOV CX,120 ;число посылаемых байтов LEA DX,PRTR_DATA ;DS:DX указывают на данные INT 21H ;посылаем данные JC PRTR_ERROR ;на обработку ошибки При возврате установленный флаг переноса индицирует ошибку, при этом AX будет содержать 5, если принтер не связан с машиной и 6 - если указан неверный номер файла. Отметим, что при использовании предопределенного номера файла ненужно открывать устройство. Низкий уровень. Байт данных посылается на принтер, путем посылки его в регистр выводимых данных, адрес порта которого совпадает с базовым адре- сом принтера. Помните, что базовые адреса для LPT1-3 хранятся со смещениями 8, 10 и 12 в области данных BIOS (начинающейся с 0040:0000). После того как данные посланы в регистр на короткое время включается бит строба регистра управления выводом, адрес порта которого на 2 больше, чем для регистра данных. Номер бита строба равен 0 и он должен быть установлен только на очень корот- кое время, чтобы инициировать передачу данных, находящихся в регистре данных. Процедура печати может немедленно сбросить бит строба обратно в 0. После того как байт данных послан, программа должна ожидать, пока принтер не сообщит, что он готов к приему следующего. Это делается двумя способами. При готовности принтер дает импульс в бит подтверждения регистра статуса ввода, адрес порта которого на 1 больше базового адреса принтера. Номер бита подтверждения равен 6 и обычно он установлен в 1. Импульс подтверждения сбрасывает этот бит в 0 на достаточно долгое время, чтобы программа на языке ассемблера могла увидеть это, если она постоянно следит за ре- гистром. Другой способ узнать, что принтер готов к приему следующего байта данных состоит в непрерывной проверке бита 7 регистра ста- туса, который сбрасывается в 0, когда принтер занят и устанавли- вается в 1, когда он готов принять данные. Если Вы пишите проце- дуру печати низкого уровня, которая должна работать в интерпрети- руемом Бейсике или другом очень медленном языке, то надо исполь- зовать этот метод. Следующий пример получает базовый адрес LPT1 из области данных BIOS и затем выводит данные из буфера, на который указывает ре- гистр BX. Программа постоянно проверяет регистр статуса на заня- тость и одновременно проверяет бит 3, чтобы проверить наличие ошибки на принтере. ;---подготовка MOV AX,40H ;ES указывает на область данных BIOS MOV ES,AX ; MOV DX,ES:[8] ;базовый адрес LPT1 в DX MOV BX,DATA_START ;BX указывает на буфер данных ;---посылаем символ NEXTCHAR: MOV AL,[BX] ;помещаем символ в AL OUT DX,AL ;посылаем символ INC DX ;DX будет указывать на регистр INC DX ;управления выводом MOV AL,13 ;цепочка битов для импульса строба OUT DX,AL ;посылаем сигнал строба DEC AL ;нормальное ссотояние регистра OUT DX,AL ;посылаем его ;---проверка на ошибку и ожидание готовности принтера DEC DX ;DX указывает на регистр статуса NOT_YET: IN AL,DX ;получаем байт статуса TEST AL,8 ;ошибка? JNZ PRTR_ERROR ;переход на обработку ошибки TEST AL,80H ;принтер занят? JZ NOT_YET ;если занят, то назад INC BX ;увеличиваем указатель в буфере данных DEC DX ;DX указывает на регистр данных JMP NEXTCHAR ;идем на печать следующего символа Когда установлен бит 4 управляющего регистра принтера, то разрешено прерывание принтера. Когда используется прерывание, то программа не должна ожидать сигнала готовности от принтера, неп- рерывно опрашивая регистр статуса принтера. Вместо этого, прог- рамма может послать символ и заниматься другими делами; когда принтер будет готов для приема следующего символа, то он пошлет сигнал подтверждения (бит 6 регистра статуса на короткое время будет установлен в 1) и автоматически будет вызвано прерывание принтера. Процедура обработки прерывания пошлет на принтер сле- дующий символ и вернет управление в программу, чтобы она могла продолжать свою работу, до тех пор пока не произойдет следующего прерывания. Когда все данные будут выведены, то прерывание должно отключить себя. Прерывание принтера во многом аналогично коммуни- кационному прерыванию, которое обсуждается в [7.1.8]. К сожалению, оборудование сделано так, что Вы не всегда можете полагаться на это свойство для первого адаптера принтера. На некоторых адапторах оно работает, а на других нет. Только в слу- чае последовательной/параллельной карты AT Вы может полагаться на него полностью. Вместо него можно использовать прерывание тайме- ра, как объяснено в [2.1.7]. Установите микросхему таймера 8253 так, чтобы прерывание происходило медленнее, чем скорость, с которой принтер обрабатывает данные. Затем напишите процедуру обработки прерывания, которая посылает на принтер очередной сим- вол каждый раз, когда происходит прерывание времени суток. Для того чтобы обеспечить надежную синхронизацию заставьте процедуру проверять бит занятости принтера регистра статуса (бит 7) и если принтер еще занят, то пусть процедура не посылает символ. 6.3.2 Выравнивание правого поля. Реальное выравнивание правого поля заключается в распределении пробелов, находящихся в конце строки, равномерно по промежуткам между словами. Некоторые принтеры имеют специальный режим, кото- рый автоматически осуществляет это выравнивание. Такую возмож- ность имеет цветной принтер IBM, посылка управляющего кода 27,77,0 заставляет электронику принтера интерпретировать посту- пающие данные и форматировать их. В других случаях принтер должен менять ширину пробелов между словами, переключаясь в графический режим, когда выводится символ пробела. В графических режимах ширина пробела может изменяться на размер до 1/6 размера символа. К сожалению, многие принтеры на некоторое время останавливаются при переключении между текстовым и графическим режимами, поэтому такой метод может оказаться слишком медленным. Другой подход состоит во вставке обычных символов пробела, распределяя их как можно более равномерно по строке. Более сложный графический под- ход описан ниже. Шаги, которые необходимо выполнить для форматирования с вырав- ниванием правого поля, следующие. Во-первых, из установок формата страницы должно быть вычислено число символов в строке. Затем необходимо подсчитать число символов, занимаемое каждым из после- довательно введенных слов, включая пробелы между словами. Отдель- ный счетчик должен подсчитывать число пробелов. Когда общая сумма символов превзойдет 80 (или ту ширину принтера, которая установ- лена), то последнее слово должно быть отброшено из этой суммы, вместе с предшествующим ему пробелом. Число оставшихся свободными позиций в строке умножается на 6, поскольку каждый символ зани- мает размер шести точек по горизонтали, а получившееся число делится на число пробелов между словами. После печати каждого слова принтер устанавливается в графичес- кий режим 480 точек в строке и посылает на принтер ряд кодов ASCII 0. Каждый такой байт сдвигает печатающую головку на одну точку вправо. Посылаемое число должно быть равно шести для обыч- ного пробела, плюс результат распределения пустого пространства. Наконец, если остаток от деления ненулевой, то надо добавить по одному добавочному байту к первым пробелам, до тех пор пока оста- ток не будет исчерпан. Для примера рассмотрим случай, когда строка состоит из 12 слов, содержащих 61 букву, плюс 11 пробелов между словами. Это оставляет в 80-тисимвольной строке 8 свободных позиций. Эти во- семь позиций, умноженные на 6 точек, составляют 48 точек дополни- тельного пространства строки. Поскольку в строке 11 пробелов, то к каждому из них должно быть добавлено по 4 дополнительные точки и после этого останутся еще 4 лишние точки, которые надо добавить по одной к первым четырем пробелам. Тогда первые 4 пробела будут иметь размер 6 точек нормального пробела, плюс добавочные 5, что в сумме равно 11. Остальные пробелы этой строки будут иметь раз- мер 10 точек. Чтобы послать эти данные на принтер, подготовьте сначала код, посылающий на принтер 1 байт ASCII 0, а затем помес- тите его в цикл, который выполняйте столько раз, сколько нужно послать таких байтов. На рис. 6-2 показан этот процесс. В нижеприведенном примере показаны основы выравнивания по правому полю. Не забудьте об обработке специальных случаев, таких как слово, которое длиннее строки (напр., длинный ряд тире). Процедура нуждается в модификации, которая позволяла бы ей иметь дело со случаем, когда строка содержит всего несколько слов, как это бывает в конце параграфа. Не позволяйте ей распределить эти слова равномерно по всей ширине страницы. Высокий уровень. В данном примере, BUFFERPTR указывает на место в буфере дан- ных, с которого начинается следующая строка, выводимая на печать. 100 S$ = "This text will be printed with right justification using the printer alternately in text modes and graphics modes." 110 STRINGPTR = 1 'указатель в строке данных S$ 120 COLUMNS = 1 'счетчик позиции в строке 130 SPACES = 0 'счетчик пробелов в строке 140 '''вычисляем сколько слов помещается в строке 150 C$ = MID$(S$,STRINGPTR,1) 'получаем символ 160 IF C$ <> " " THEN 190 'если не пробел, то вперед 170 LASTSPACE = COLUMNS 'иначе зпоминаем позицию 180 SPACES = SPACES + 1 'увеличиваем число пробелов 190 COLUMNS = COLUMNS+1 'увеличиваем указатель столбца 200 STRINGPTR = STRINGPTR + 1 'увеличиваем указатель данных 210 IF COLUMNS = 81 THEN 230 'уход по концу строки 220 GOTO 150 'иначе к следующему символу 230 IF C$ <> " " THEN 270 'если последний не пробел, то уход 240 COLUMNS = 79 'иначе длина строки равна 79 250 SPACES = SPACES - 1 'отнимаем последний пробел 260 GOTO 340 'идем на вычисление пробелов 270 C$ = MID$(S$,STRINGPTR+1,1) 'проверяем на конец слова 280 IF C$ <> " " THEN 300 'если не пробел, то уход 290 GOTO 340 'иначе на вычисление пробелов 300 COLUMNS = COLUMNS - LASTSPACE 'возвращаемся к концу слова 310 STRINGPTR = STRINGPTR - COLUMNS + 1 'возвращаем указатель 320 COLUMNS = LASTSPACE - 1 'убираем последний пробел 330 SPACES = SPACES - 1 'уменьшаем число пробелов 340 '''вычисляем число точек на пробел 350 EXTRASPACES = 80 - COLUMNS 'вычисляем число пробелов в конце 360 TOTALSPACES = EXTRASPACES + SPACES 'добавляем к пробелам 370 TOTALDOTS = 6*TOTALSPACES 'вычисляем число точек 380 DOTSPERSPC = TOTALDOTS/SPACES 'получаем число точек на пробел 390 EXTRADOTS = TOTALDOTS MOD SPACES 'остаток от деления 400 '''теперь печатаем первую строчку нашего текста 410 OPEN "LPT1:" AS #1 'открываем принтер 420 PRINTPTR = 1 'указатель на начало буфера данных 430 C$ = MID$(S$,PRINTPTR,1) 'берем символ 440 PRINTPTR = PRINTPTR + 1 'увеличиваем указатель 450 IF C$ = " " THEN 500 'если пробел, то на обработку пробела 460 PRINT #1, C$ 'иначе печатаем символ 470 IF PRINTPTR = COLUMNS + 1 THEN 590 'выход по концу строки 480 GOTO 430 'иначе печатаем следующий символ 490 '''вот процедура печати пробелов 500 PRINT #1, CHR$(27) + "K"; 'переход в графический режим 510 NUMBERDOTS = DOTSPERSPC 'вычисляем число байтов ASCII 0 520 IF EXTRADOTS = 0 THEN 550 'если нет добавочных точек, уход 530 NUMBERDOTS = DOTSPERSPC + 1 'иначе добавляем точку 540 EXTRADOTS = EXTRADOTS - 1 'уменьшаем число добавочных точек 550 PRINT #1, CHR$(NUMBERDOTS); 'посылаем число графических байт 560 PRINT #1, CHR$(0); ' 570 FOR N = 1 TO NUMBERDOTS: PRINT #1, CHR$(0): NEXT 580 GOTO 430 'пробел окончен, идем на след. символ 590 PRINT #1, CHR$(13) 'в конце печатаем возврат каретки Низкий уровень. Соответствующая ассемблерная процедура слишком длинная, чтобы приводить ее здесь. Она работает в точности так же, как и проце- дура на Бейсике, за исключением того, что нет необходимости заво- дить отдельную переменную, чтобы хранить строку. Нужно просто установить указатели на начало и конец строки, которую надо напе- чатать в буфере данных. 6.3.3 Пропорциональная печать. Вообще говоря, пропорциональная печать требует специального принтера, который хранит в ПЗУ информацию о ширине каждого симво- ла. Цветной принтер IBM имеет режим пропорциональной печати, который включается последовательностью 27,78,1, а выключается - 27,78,0. Программа, которая форматирует вывод на такой принтер, должна знать информацию о ширине каждого символа (ее можно найти в документации). Имея эту информацию, она может вычислить сколько слов поместится на одной строке. Имейте ввиду, что некоторые матричные принтеры автоматически выводят пропорциональный текст в режиме за два прохода. Если слова в строке разделяются добавочными пробелами в графическом режиме, то принтер может переходить ко второму проходу после печати каждого слова, вместо того, чтобы повторить сразу всю строку. Поскольку принтеры относительно медленно меняют направле- ние перемещения печатающей головки, то в этом случае печать текс- та, выравненного по правому краю, в пропорциональном режиме может занимать очень много времени и оказывается непосильной ношей для принтера. Эта проблема не возникает при однонаправленной пропор- циональной печати. Отметим, что цветной принтер IBM может автома- тически комбинировать пропорциональную печать с автоматическим выравниванием правого края, что делает специальное программирова- ние ненужным. Изощренные программисты могут заставить любой графический принтер печатать в пропорциональном режиме. Программа должна иметь в памяти картину битов для каждого символа (см. [6.3.4]). Вместо того, чтобы посылать на принтер код ASCII, который вызы- вает изображение символа из ПЗУ, используется данная цепочка битов для создания графического изображения строки текста. Затем вся нужная строка данных выводится на принтер в графическом режи- ме. Этот подход расходует много памяти на хранение графических образов символов, однако он позволяет полностью контролировать выводимое изображение. Высокий уровень. В данном примере включается режим пропорциональной печати, а затем выводится первая строка выходных данных программы. Ширина пропорционального шрифта считывается в массив FONTWIDTH из после- довательного файла. 100 '''считываем массив ширин шрифта 110 DIM FONTWIDTH(127) 'отводим массив для ширин 120 OPEN "FONTS" FOR INPUT AS #1 'открываем файл ширин 130 FOR N = 32 TO 127 'хранятся ширины для кодов 32-127 140 INPUT #1, FONTWIDTH(N) 'читаем ширину из массива 150 NEXT 'следующий элемент 160 '''вычисляем сколько символов поместится в строке 170 CHARPTR = 0 'указатель в буфере 180 LINE$ = "" 'хранит строку для вывода 190 LINELENGTH = 0 'счетчик длины в точках 200 WHILE LINELENGTH <480 'добавляем до заполнения строки 210 C$ = PEEK(BUFFERPTR+CHARPTR) 'берем символ из буфера данных 220 LINELENGTH = LINELENGTH + FONTWIDTH(ASC(C$)) 230 LINE$ = LINE$+C$ 'добавляем к строке вывода 240 CHARPTR = CHARPTR+1 'увеличиваем указатель 250 WEND 'на обработку следующего символа 260 '''по концу строки возвращаемся к концу последнего слова 270 IF C$ = "" THEN 310 'если последний пробел, то уход 280 FOR N = LEN(LINE$) TO 1 STEP -1 'идем назад от конца 290 IF MID$(LINE$,N,1) = " " THEN 310 'этот символ пробел? 300 NEXT 'есчли нет, то берем следующий 310 LINELENGTH = N - 1 'если да, то предыдущий - последний 320 '''инициализируем пропорциональную печать и посылаем данные 330 LPRINT CHR$(27);CHR$(78);CHR$(1); 'управляющие коды 340 FOR N = 1 TO LINELENGTH 'для каждого символа 350 LPRINT PEEK(BUFFERPTR+N-1); 'печатаем его 360 NEXT 'и идем на следующий символ Низкий уровень. Программа на языке ассемблера должна работать совершенно ана- логично приведенному бейсиковскому примеру. Одно из преимуществ ассемблера состоит в том, что для просмотра ширин символов можно использовать инструкцию XLAT. Поместите символ в AL, DS:DX должны указывать на таблицу, после чего можно использовать XLAT. Ширина символа будет возвращена в AL: ;---просмотр ширин символов LEA SI,DATA_BUFFER ;указываем на буфер данных LEA BX,WIDTH_TABLE ;указываем на таблицу ширин MOV AL,[SI] ;получаем байт данных XLAT WIDTH_TABLE ;теперь его ширина в AL 6.3.4 Печать специальных символов. Большинство принтеров не поддерживают расширенный набор симво- лов IBM, однако большинство программ использует специальные сим- волы псевдографики. Очень полезно иметь возможность печатать эти символы и не так сложно это сделать на любом матричном принтере, который имеет графические возможности. Вместо того, чтобы пола- гаться на ПЗУ принтера, программа должна сама создавать эти сим- волы и она должна обращаться с принтером определенным образом, чтобы они были напечатаны на бумаге. Сама по себе печать специальных символов тривиальна. Просто разбейте символ на шесть байтов, цепочка битов каждого соответст- вует структуре точек в каждом из шести столбцов точек, составляю- щих символ. Например, чтобы напечатать символ горизонтальной двойной черты, код ASCII которого 205, программа должна вывести цепочку битов 00100100 шесть раз в режиме 480 точек в строке. Это количество в точности соответствует ширине символа, поскольку 6/480 равно 1/80 строки. Чтобы перевести принтер именно в этот графический режим необходимо подать управляющий код 27,75. Затем пошлите число идущих вслед графических данных, которое передается в виде пары байт, причем младший байт первый. Наконец, идут сами 6 битов данных, которые в данном случае равны сумме значений битов 2 и 5 (4 + 32 = 36). Вся последовательность целиком выгля- дит так: 27, 75, 6, 0, 36, 36, 36, 36, 36, 36. Для более высокого разрешения могут быть использованы более точные графические режи- мы; вообще говоря добавочные расходы времени машины ничтожны, по сравнению со скоростью операций принтера. Имеется частная проблема, когда символы псевдографики должны соприкасаться друг с другом по вертикали. Обычно принтеры печа- тают строку, состоящую из столбца восьми точек, затем спускаются вниз на высоту 12 точек, оставляя тем самым поле размером в 4 точки между строками символов. Символы псевдографики должны печа- таться и в этом поле, а в некоторых случаях они занимают в высоту 12 точек. Поскольку большинство печатающих головок имеет только 8 иголок, то единственным решением проблемы является печать таких символов за два прохода, продвигая бумагу вперед перед вторым проходом. В этом случае символ перевода строки (ASCII 10) вообще не используется. Вместо этого, принтер попеременно делает интер- валы высотой то в 8, то в 4 точки. При втором проходе часть иго- лок будут на том месте, где уже имеются отпечатанные точки, поэ- тому надо чтобы биты для этих иголок были сброшены в 0, чтобы они не работали. Чтобы продвинуть бумагу на высоту четырех точек надо послать код 27, 65, 4, 27, 50, а на высоту восьми точек - 27, 65, 8, 27, 50. При этом вызывается автоматический возврат каретки. В то время когда выполняется первый проход, готовится временная строка текста, которая будет печататься при втором проходе. Если данный символ обычный, то в соответствующую позицию временной второй строки символов надо поместить пробел (ASCII 32). Но если встречается специальный графический символ, который должен печа- таться в четырехточечном поле, т