КаталогИндекс раздела
НазадОглавлениеВперед


8. Принтер

Принтер является, пожалуй, самым "неудобным" периферийным устройством ПЭВМ. Это "неудобство" заключается в том, что IBM-совместимые машины комплектуются принтерами разных производителей, которые подчас отличаются друг от друга по управлению. В качестве стандарта для IBM-совместимых машин принят принтер Epson. Принтеры других фирм ориентированы на этот стандарт в той или иной степени. Во всяком случае, можно с уверенностью утверждать, что вывод символа на принтер всегда выполняется одинаково. Что же касается управления спецификациями печати, то здесь гарантировать точное совпадение нельзя, к сожалению, не гарантируется и одинаковая установка разрядов байта состояния. Мы в нашем описании и в наших программах ориентируемся на стандарт Epson.

8.1. Порты принтера

DOS может работать с тремя параллельными принтерами, именуемыми LPT1, LPT2, LPT3. Каждый принтер имеет по три порта: порт вывода (базовый порт), порт состояния и порт управления. Адреса портов строго не фиксированы. В области данных BIOS по адресам 0040:0008, 0040:000A, 0040:000C содержатся адреса базовых портов для LPT1, LPT2, LPT3 соответственно. Адрес порта состояния - на 1 больше базового, порта управления - еще на 1 больше.

Самая первая операция, которую мы рассмотрим для принтера, - определение его состояния. Разряды байта, считываемого из порта состояния принтера, интерпретируются следующим образом:

0- 2 - не используются, обычно установлены в 1;
3- ошибка принтера - нет/есть (0/1);
4- принтер подключен/не подключен (1/0);
5- бумага есть/нет (0/1);
6- принтер выводит очередной символ/готов (0/1);
7- принтер занят/свободен (0/1).
При возможных расхождениях в интерпретации этого байта для разных принтеров наиболее информативен, по-видимому, бит 3, его установка в 1 говорит о готовности принтера.

Программа примера 8.1 предлагает проверить байт состояния при некоторых, наиболее вероятных состояниях принтера. Для сравнения программа выдает байт стандарта Epson.


/*==                     ПРИМЕР 8.1                     ==*/
/*============== Получение статуса принтера ==============*/
#include <dos.h>
main() {
  union REGS rr;
  int dataport,statusport,ctrlport;   /* Номера портов */
  unsigned char stat; /* Байт статуса */
  int i;
  /* Определение адресов портов принтера */
  dataport=peek(0x40,8);
  statusport=dataport+1;
  ctrlport=statusport+1;
  printf("Порты LPT1 = %03X, %03X, %03X\n",
    dataport,statusport,ctrlport);
  /* Проверка состояний */
  printf("\nУстановите: принтер выключен. ");
  printf("Нажмите любую клавишу\n");
  getch();
  stat=inportb(statusport);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((stat>>i)&1) printf("1"); else printf("0");
  printf("\nEpson состояние    - 11110111\n");
  printf("\nУстановите: принтер offline. ");
  printf("Нажмите любую клавишу\n");
  getch();
  stat=inportb(statusport);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((stat>>i)&1) printf("1"); else printf("0");
  printf("\nEpson состояние    - 01010111\n");
  printf("\nУстановите: нет бумаги. ");
  printf("Нажмите любую клавишу\n");
  getch();
  stat=inportb(statusport);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((stat>>i)&1) printf("1"); else printf("0");
  printf("\nEpson состояние    - 01110111\n");
  printf("\nУстановите: принтер готов. ");
  printf("Нажмите любую клавишу\n");
  getch();
  stat=inportb(statusport);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((stat>>i)&1) printf("1"); else printf("0");
  printf("\nEpson состояние    - 11011111\n");
}

Разряды байта, передаваемого в порт управления, интерпретируются следующим образом:

0- устанавливается в 1 при передаче байта данных (строб вывода);
1- установка его в 1 вызывает автоматический перевод строки после возврата каретки;
2- нормально установлен в 0, устанавливается в 1 при инициализации порта принтера;
3- устанавливается в 1 при выборе принтера;
4- разрешает (1) прерывание принтера, используется только в программе спуллинга печати;
5 - 6- не используются устанавливаются в 0.
Обычное состояние порта управления - 00001100.
Перед началом работы с принтером необходимо инициализировать его порт - на 50 мсек выставить 0 в бите 2 порта управления.
Действия при выводе символов на принтер должны быть следующими: Эта последовательность действий реализована в программе 8.2. Обратите внимание на то, что последовательность символов, выводимая в этой программе начинается символом с кодом 24, а заканчивается символом с кодом 10. Дело в том, что принтеры Epson (и большинство других) имеют внутренний буфер, в котором накапливаются выдаваемые на принтер коды. Символ с кодом 24 не имеет отображения на печати, это управляющий символ, который вызывает очистку буфера. Символ с кодом 10 вызывает выдачу содержимого буфера на печать и перевод строки. Если исключить последний символ, то при выполнении нашей программы на бумаге ничего не отпечатается, все выданные нами символы останутся в буфере принтера. Если исключить первый символ, то перед нашим выводом на печать могут выйти символы, возможно оставшиеся в буфере принтера от выполнения предыдущей программы.


/*==                      ПРИМЕР 8.2                    ==*/
/*=============== Печать символа на принтере =============*/
#include <dos.h>
main()  {
  int p1,p2,p3,i; /* Номера портов */
  unsigned char stat; /* Байт состояния */
  /* Символы для печати (10 - перевод строки) */
  char a[]={24,'a','b','c','d','e','f','g','h','i','j',10};
  /* Опеределение портов */
  p1=peek(0x40,8); p2=p1+1; p3=p2+1;
  /* Инициализация принтера */
  outportb(p3,0x08); delay(50); outportb(p3,0x0c);
  /* Проверка состояния */
  stat=inport(p2);
  if ((stat&0x20)!=0) {
    printf("Ошибка принтера. Байт состояния = %02x\n",stat);
    exit();
    }
  for(i=0;i<12;i++) {
    /* Ожидание готовности */
    while ((stat&0x80)==0) stat=inport(p2);
    /* Символ в базовый порт */
    outportb(p1,a[i]);
    /* Строб */
    outportb(p3,0x0d);  outportb(p3,0x0c);
    /* Проверка состояния */
    stat=inport(p2);
    if ((stat&0x20)!=0) {
      printf("Ошибка принтера. Байт состояния = %02x\n",stat);
      exit();
      }
    }
}

8.2. Прерывание BIOS

В BIOS принтер обслуживается прерыванием 0x17, имеющим функции:

0- вывод символа;
1- инициализация;
2- чтение состояния.
Все три функции возвращают состояние принтера в регистре AH.

Программные примеры 8.3 и 8.4 аналогичны по выполняемым действиям двум первым, но используют прерывание 0x17.


/*==                     ПРИМЕР 8.3                     ==*/
/*============== Получение статуса принтера ==============*/
#include <dos.h>
main() {
  union REGS rr;
  int i;
  /* Проверка состояний */
  printf("\nУстановите: принтер выключен. ");
  printf("Нажмите любую клавишу\n");
  getch();
  rr.x.dx=0; /* Номер принтера - LPT1 */
  rr.h.ah=2; /* Функция 2 */
  int86(0x17,&rr,&rr);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((rr.h.ah>>i)&1) printf("1"); else printf("0");
  printf("\n\nУстановите: принтер offline. ");
  printf("Нажмите любую клавишу\n");
  getch();
  rr.x.dx=0; rr.h.ah=2; int86(0x17,&rr,&rr);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((rr.h.ah>>i)&1) printf("1"); else printf("0");
  printf("\n\nУстановите: нет бумаги. ");
  printf("Нажмите любую клавишу\n");
  getch();
  rr.x.dx=0; rr.h.ah=2; int86(0x17,&rr,&rr);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((rr.h.ah>>i)&1) printf("1"); else printf("0");
  printf("\n\nУстановите: принтер готов. ");
  printf("Нажмите любую клавишу\n");
  getch();
  rr.x.dx=0; rr.h.ah=2; int86(0x17,&rr,&rr);
  printf("Состояние принтера - ");
  for (i=7; i>=0; i--)
    if ((rr.h.ah>>i)&1) printf("1"); else printf("0");
}

/*==                      ПРИМЕР 8.4                    ==*/
/*=============== Печать символа на принтере =============*/
#include <dos.h>
main() {
  union REGS rr;
  unsigned char stat; /* Байт состояния */
  /* Символы для печати */
  char a[]={24,'a','b','c','d','e','f','g','h','i','j',10};
  int i;
  /* Инициализация принтера */
  rr.x.dx=0;  /* Номер принтера - LPT1 */
  rr.h.ah=1;  /* Функция 1 */
  int86(0x17,&rr,&rr);
  stat=rr.h.ah;
  /* Проверка состояния */
  if ((stat&0x08)!=0) {
    printf("Ошибка принтера. Байт состояния = %02x\n",stat);
    exit();
    }
  for(i=0;i<12;i++) {
    /* Ожидание готовности */
    while ((stat&0x80)==0) {
      rr.x.dx=0; /* Номер принтера - LPT1 */
      rr.h.ah=2; /* Функция 2 */
      int86(0x17,&rr,&rr);
      stat=rr.h.ah;
      }
    /* Вывод символа */
    rr.x.dx=0; /* Номер принтера - LPT1 */
    rr.h.ah=0; /* Функция 0 */
    rr.h.al=a[i];
    int86(0x17,&rr,&rr);
    /* Проверка состояния */
    if ((rr.h.ah&0x08)!=0) {
      printf("Ошибка принтера. Байт состояния = %02x\n",stat);
      exit();
      }
    }
}

8.3. Функции DOS

В DOS имеется функция 5, которая интегрирует в себе операции по управлению, выводу и анализу состояния. Те же действия программируются при помощи функции DOS следующим образом (пример 8.5):


/*==                      ПРИМЕР 8.5                    ==*/
/*=============== Печать символа на принтере =============*/
#include <dos.h>
main() {
  union REGS rr;
  /* Символы для печати */
  char a[]={24,'a','b','c','d','e','f','g','h','i ','j',10};
  int i;
  for(i=0;i<12;i++) {
    rr.h.ah=5;  /* Функция 5 DOS */
    rr.h.dl=a[i];
    intdos(&rr,&rr);
    }
}

Кроме того, с принтером в DOS связан стандартный файл печати stdprn. Для вывода на принтер можно использовать функцию файлового вывода 0x40, указывая для нее номер файла - 4.

8.4. Управление спецификациями печати

Выше мы уже встретились с управляющими символами (24,10). Некоторые коды или последовательности кодов интерпретируются принтером не как коды символов, подлежащих отображению на бумаге, а как управляющие коды. Они используются для выполнения принтером специальных действий и управления спецификациями печати. Наиболее интересный из этих кодов - код 0x1B (27) или Esc. Появление этого кода интерпретируется принтером как начало целой управляющей последовательности. (Esc-последовательности принтера в отличие от клавиатуры и терминала обрабатываются не драйвером ANSI, а аппаратурой принтера). Следующий за Esc код задает тип действия, далее могут следовать еще несколько байт, количество которых зависит от действия. В примере 8.6 продемонстрированы некоторые режимы печати, устанавливаемые при помощи управляющих кодов и Esc-последовательностей. (Этот и следующие примеры не исчерпывают возможностей управления принтером при помощи управляющих кодов и Esc-последовательностей. Для получения полного представления о них следует обратиться к техническому описанию принтера.) Обратите внимание на последовательность 27, 64, названную в комментариях инициализацией. Не следует путать эту инициализацию с инициализацией порта, о которой шла речь выше. Esc-последовательность инициализации обеспечивает установку всех спецификаций печати в те значения, которые они имеют по умолчанию. Для выдачи управляющих последовательностей (как и печатаемых символов) можно использовать средства BIOS, DOS, а также любые средства, имеющиеся в применяемом языке программирования. Мы в этом и следующих примерах применяем функцию Турбо-Си putc.


/*==                      ПРИМЕР 8.6                    ==*/
/*============ Управляющие коды режимов печати ===========*/
#include <stdio.h>
#define prt(x) putc(x,stdprn);
main() {
  /* Инициализация */__  _.prt(27); prt(64);
  /* Обычный режим */
  fprintf(stdprn,"Default mode\n");
  /* Режим двойной ширины */
  prt(14);
  fprintf(stdprn,"Set double width mode\n");
  prt(20);
  fprintf(stdprn,"Close double width mode\n");
  /* Режим плотной печати */
  prt(15);
  fprintf(stdprn,"Set empassed mode\n");
  prt(18);
  fprintf(stdprn,"Close empassed mode\n");
  /* Режим подчеркивания */
  prt(27); prt(0x2d); prt(1);
  fprintf(stdprn,"Set underline mode\n");
  prt(27); prt(0x2d); prt(0);
  fprintf(stdprn,"Close underline mode\n");
  /* Режим двойной жирности */
  prt(27); prt(0x45);
  fprintf(stdprn,"Set double strike mode\n");
  prt(27); prt(0x46);
  fprintf(stdprn,"Close double strike mode\n");
  /* Режим верхних индексов печати */
  prt(27); prt(0x53); prt(0);
  fprintf(stdprn,"Set superscript mode\n");
  prt(27); prt(0x54);
  fprintf(stdprn,"Close superscript mode\n");
  /* Режим нижних индексов печати */
  prt(27); prt(0x53); prt(1);
  fprintf(stdprn,"Set subscript mode\n");
  prt(27); prt(0x54);
  fprintf(stdprn,"Close subscript mode\n");
}

Отдельный программный пример иллюстрирует интересную возможность формирования пользователем собственных печатных символов. Образы выводимых символов хранятся в ПЗУ принтера, но в принтере есть еще и ОЗУ, в которое могут быть загружены образы, созданные пользователем. Образ формируется на сетке высотой 8 и шириной 11 точек. В памяти образ представляется в двоичном виде, причем один байт описывает один столбец образа. Сформировав двоичный образ символа, следует записать его в ОЗУ, для чего используется Esc-последовательность вида:

Теперь для печати такого оригинального символа следует переключить принтер на ОЗУ (последовательность 0x1B, 0x25, 1) и выдать на принтер код символа в ОЗУ. Для обратного переключения на ПЗУ выдается последовательность 0x1B, 0x25, 0.

Программа следующего примера использует технику, часто применяемую в драйверах русского текста (образы букв кириллицы необязательно есть в ПЗУ принтера). Программа загружает в ОЗУ принтера образ перевернутой буквы "a" под номером 40 и перехватывает вектор прерывания 0x17. Хотя в main-функции используются для печати функции Турбо-Си, все операции печати сводятся к этому прерыванию. Обработчик прерывания прежде всего анализирует функцию прерывания. Для функций 1 и 2 он вызывает старый обработчик. Для функции 0 (вывод символа) анализируется код символа в регистре AL. Если это код "a", то обработчик переключает принтер на ОЗУ, выводит код 40 и переключает принтер обратно на ПЗУ. Любой другой код обработчик выводит из ПЗУ.


/*==                      ПРИМЕР 8.7                    ==*/
/*=================== Загружаемый шрифт ==================*/
#include <dos.h>
#include <stdio.h>
void interrupt (* old17)();
void interrupt h17();
int p1,p2,p3; /* Номера портов принтера */
#define prt(x) putc(x,stdprn)
#define byte unsigned char
/*==== main ====*/
main() {
  char *st[]={  /* Текст для печати */
  "He's a real Nowhere Man,",
  "Sitting in his Nowhere Land,",
  "Making all his nowhere plan for nobody." ;
  byte esct[]= { /* Esc-последовательность */
    27, 38, 0, /* Начало загрузки шрифта */
    40, 40, /* Номера начального и конечного кодов */
    0x8a, /* Ширина символа - 0a и сдвиг 80 */
    /* Образ символа */
    32,28,34,42,42,42,42,42,16,0,0 };
  int i;
  prt(27); prt(64); /* Инициализация принтера */
  /* Выдача Esc-последовательности */
  for (i=0; i<17; prt(esct[i++]));
  p1=peek(0x40,8); p2=p1+1; p3=p2+1; /* Опр.портов */
  /* Подключение к вектору 17 */
  *old17=getvect(0x17); setvect(0x17,h17);
  /* Печать текста */
  for(i=0;i<3; fprintf(stdprn,"%s\n",st[i++]));
  setvect(0x17,old17); /* Восстановление вектора */
  prt(27); prt(64); /* Инициализация принтера */
}
/*==== Обработчик 17-го прерывания ====*/
void interrupt h17() {
  if (_AH==0) { /* Вывод символа */
    if (_AL=='a') { /* Символ "a" - особая обработка */
      /* Esc-послед. переключения на шрифт ОЗУ. */
      ownchar(27); ownchar(0x25); ownchar(1);
      /* Печать символа ОЗУ с кодом 40 */
      ownchar(40);
      /* Esc-послед. переключения на шрифт ПЗУ. */
      ownchar(27); ownchar(0x25); ownchar(0);
      }
    else ownchar(_AL); /* Печать символа не "a". */
    }
  else (*old17)();  /* Старый обработчик */
}
/*==== Печать одного символа ====*/
ownchar(byte f) {
 byte stb; /* Байт состояния */
  /* Ожидание готовности */
  stb=0; while ((stb&0x80)==0) stb=inport(p2);
    outportb(p1,f); /* Вывод символа */
  outportb(p3,0x0d); outportb(p3,0x0c); /* Строб */
}

Напрашивается мысль о том, что матричный принтер можно заставить выводить на печать не только символы, но и любые изображения, и такая возможность действительно имеется - это использование принтера в графическом режиме. При печати в графическом режиме за один проход каретки печатается строка (полоса) высотой 8 точек. Таким образом, графическое изображение составляется из таких строк-полос. Размер битового образа одной строки в памяти (в байтах) равен ширине строки (в точках). Каждый байт образа описывает один столбик изображения высотой в 8 и шириной в 1 точку (старший разряд байта соответствует верхней точке изображения). Для выдачи образа на печать следует выдать на принтер такую Esc-последовательность:

В примере 8.8 приведена программа, которая строит изображение на экране, а затем выводит на принтер его твердую копию. Для нас здесь представляет интерес функция prtgraph, обеспечивающая вывод твердой копии (разобраться в функции paint, строящей изображение на экране, читатель сможет, ознакомившись с главой, посвященной видеоадаптеру).

Параметрами функции prtgraph являются экранные координаты левого верхнего и правого нижнего углов того окна экрана, копию которого следует вывести на печать, и код цвета, которым построено изображение. Поскольку изображение будет выводиться построчно, следует обеспечить, чтобы строки на печати располагались плотно, без промежутков между ними, этому удовлетворяет расстояние между строками 25/216 дюйма, которое устанавливается специальной Esc-последовательностью. Определяется ширина окна, размер битового образа будет равен этой ширине, в соответствии с этим выделяется память для размещения битового образа, заодно размер образа заносится в третий и четвертый байты Esc-последовательности установки графического режима. Формирование образа происходит построчно: последовательно считываются (функцией 0x0D прерывания 0x10, см. раздел 9.7) точки первой строки экранного обpаза; x-координата внутри окна опеределяет номер байта печатного образа, y-координата - номер формируемого разряда в этом байте. Если точка на экране имеет заданный цвет, то в соответствующий разряд заносится 1. (Мы считываем образ по строкам, и это соответствует представлению графической информации в видеоадаптере, но вы можете организовать и считывание образа по вертикали.) Когда очередные 8 строк экранного образа будут считаны и преобразованы в образ печати, информация выводится на принтер - вначале заголовок Esc-последовательности, а затем сам образ, после чего начинается формирование следующей строки образа для печати. Поскольку высота заданного окна не обязательно кратна 8, после перебо- ра всех экранных строк может остаться невыведенным на печать образ печатной строки, высота которого менее 8 точек. Этот образ выводится после выхода из цикла.


/*==                    ПРИМЕР 8.8                      ==*/
/*========== Работа принтера в графическом режиме ========*/
#include <dos.h>
#include <math.h>
#include <stdio.h>
#include <alloc.h>
#define prt(x) putc(x,stdprn);
union REGS rr;
main() {
int col=4;      /* Цвет - красный */
  paint(col);              /* Построение картинки */
  getch();
  prtgraph(1,1,80,80,col); /* Вывод на принтер */
  }
/* Построение картинки (из окружностей) */
paint(int col) {
  int x,y,fy,fx,k;
  /* Установка графического режима */
  rr.x.ax=0x0010; int86(0x10,&rr,&rr);
  /* Построение контуров */
  for (x=-24; x<=24; x++) {
    y=(int)sqrt((double)(576-x*x));
    point(x+32,32-y,col); point(x+32,32+y,col);
    }
  for (x=-12; x<=12; x++) {
    y=(int)sqrt((double)(144-x*x));
    point(x+20,32+y,col); point(x+44,32-y,col);
    }
  for (x=-4; x<=4; x++) {
    y=(int)sqrt((double)(16-x*x));
    point(x+20,32-y,col); point(x+20,32+y,col);
    point(x+44,32+y,col); point(x+44,32-y,col);
    }
  /* Закрашивание */
  for (fx=-24; fx<=24; fx++) {
    fy=(int)sqrt((double)(576-fx*fx)); x=fx+32;
    for (k=0, y=32-fy; y<=32+fy; y++) {
      rr.x.dx=y; rr.x.cx=x; rr.h.bh=0;
      rr.h.ah=0x0d; int86(0x10,&rr,&rr);
      if ((rr.h.al==col)&&((y!=32)||(x==32))) k=1-k;
      if (k) point(x,y,col);
      }
    }
}
/* Вывод одной графической точки */
point(int x, int y, int c) {
  rr.x.dx=y; rr.x.cx=x;  rr.h.bh=0; rr.h.al=c;
  rr.h.ah=0x0c; int86(0x10,&rr,&rr);
  }
/*----------------------------------------------------*/
/* Графическая копия экрана.
          (x1,y1),(x2,y2) - координаты окна, c - цвет */
prtgraph(int x1, int y1, int x2, int y2, int c) {
  char *str;  /* Графический образ строки для печати */
  char *s;
  int strsize;   /* Размер образа */
  char esc[] =   /* Начало Esc-послед.графической печати */
     { 27,42,0,0,0 };
  int x,y;   /* Экранные координаты */
  int bit;   /* Счетчик разрядов в образе */
  int i;
  /* Инициализация принтера */
  prt(27); prt(64);
  /* Установка расстояния между строк */
  prt(27); prt(51); prt(25);
  /* Выделение памяти для образа */
  strsize=x2-x1+1; str=malloc(strsize);
  for (s=str, i=0; i<strsize; i++, s++) *s=0;
  /* Запись размера образа в Esc-послед. */
  esc[3]=strsize%256;  esc[4]=strsize/256;
  /* Перебор строк */
  for (bit=7, y=y1; y<=y2; y++) {
    /* Перебор точек в строке */
    for (s=str, x=x1; x<=x2; x++, s++) {
      /* Чтение точки */
      rr.x.dx=y; rr.x.cx=x; rr.h.bh=0;
      rr.h.ah=0x0d; int86(0x10,&rr,&rr);
      /* Если цвет точки совпадает -
         заносится 1 в соответствующий разряд образа */
      if (rr.h.al==c) *s|=(1<<bit);
      }
    if (--bit<0) {
      /* Если сформированы 8 разрядов образа, то:
         выводится начало Esc-последовательности, */
      for(i=0;i<5;i++) prt(esc[i]);
      /* выводится образ и обнуляется, */
      for (i=0, s=str; i<strsize; i++,s++) {
        prt(*s); *s=0;
        }
      prt(10);  /* перевод строки */
      bit=7;
      }
    }
  /* Если не весь образ выведен - вывод остатка */
  if (bit<7) {
    for (i=0; i<5; i++) prt(esc[i]);
    for (i=0, s=str; i<strsize; i++,s++) prt(*s);
    prt(10);
    }
  /* Инициализация принтера */
  prt(27); prt(64);
}


КаталогИндекс раздела
НазадОглавлениеВперед