Аппаратное обеспечение IBM PC

  35790931     

Аппаратная реализация


Компьютер может быть оснащен одним или двумя портами последовательной передачи данных. Эти порты расположены либо на материнской плате, либо на отдельной плате, вставляемой в слоты расширения материнской платы.

Бывают также платы, содержащие 4 или 8 портов последовательной передачи данных. Их часто используют для подключения нескольких компьютеров или терминалов к одному, центральному, компьютеру.

В основе последовательного порта передачи данных лежит микросхема Intel 8250. Это универсальный асинхронный приемо-передатчик (UART - Universal Asynchronous Receiver Transmitter). Микросхема содержит несколько внутренних регистров, доступных через команды ввода/вывода.

Микросхема 8250 содержит регистры передатчика и приемника данных. При передаче байта он записывается в буферный регистр передатчика, откуда затем переписывается в сдвиговый регистр передатчика. Байт "выдвигается" из сдвигового регистра по битам.

Аналогично имеются сдвиговый и буферный регистры приемника.

Программа имеет доступ только к буферным регистрам, копирование информации в сдвиговые регистры и процесс сдвига выполняется микросхемой 8250 автоматически.

Внешние устройства подключаются к порту ввода/вывода через разъем DB25P (имеющий 25 выводов) или DB9P (имеющий 9 выводов). Приведем разводку разъема последовательной передачи данных DB25P:

Номер контакта Назначение контакта Вход или выход

1 Защитное заземление -

2 Передаваемые данные Выход (Transmitted Data)

3 Принимаемые данные Вход (Received Data)

4 Запрос для передачи Выход (Request to send, RTS)

5 Сброс для передачи Вход (Clear to Send, CTS)

6 Готовность данных Вход (Data Set Ready, DSR)

7 Сигнальное заземление -

8 Детектор принимаемого Вход с линии сигнала (Data Carrier Detect, DCD)

9-19 Не используются

20 Готовность выходных Выход данных (Data Terminal Ready, DTR)

21 Не используется

22 Индикатор вызова Вход (Ring Indicator, RI)



23-25 Не используется

Наряду с 25-контактным разъемом часто используется 9-контактный разъем:


Номер контакта Назначение контакта Вход или выход

1 Детектор принимаемого Вход с линии сигнала (Data Carrier Detect, DCD)

2 Принимаемые данные Вход (Received Data)

3 Передаваемые данные Выход (Transmitted Data)

4 Готовность выходных Выход данных (Data Terminal Ready, DTR)

5 Сигнальное заземление -

6 Готовность данных Вход (Data Set Ready, DSR)

7 Запрос для передачи Выход (Request to send, RTS)

8 Сброс для передачи Вход (Clear to Send, CTS)

9 Индикатор вызова Вход (Ring Indicator, RI)

Уровни напряжения на линиях разъема составляют для логического нуля -15 вольт, для логической единицы - +15вольт.

Доступ к отдельным линиям возможен через порты ввода/вывода асинхронного адаптера, которые мы рассмотрим в следующем разделе. Там же будет описано назначение отдельных линий разъема.


Инициализация асинхронного адаптера


Первое, что должна сделать программа, работающая с асинхронным адаптером - установить протокол обмена и скорость передачи данных. После загрузки операционной системы для асинхронных адаптеров устанавливается скорость 2400 бод, не выполняется проверка на четность, используются один стоповый бит и восьмибитовая длина передаваемого символа. Вы можете изменить этот режим командой MS-DOS MODE.

Выполнив ввод из порта 3FBh, программа может получить текущий режим адаптера. Для установки нового режима измените нужные вам поля и запишите новый байт режима по адресу 3FBh.

Если вам надо задать новое значение скорости обмена данными, перед записью байта режима установите старший бит этого байта в 1. Затем последовательно двумя командами вывода загрузите делитель частоты тактового генератора. Младший байт запишите в порт 3F8h, старший - в порт 3F9h.

Перед началом работы необходимо также проинициализировать регистр управления прерываниями (порт 3F9h), даже если в вашей программе не используются прерывания от асинхронного адаптера. Если прерывания вам не нужны, запишите в этот порт значение 0.

На этом инициализацию можно считать законченной.

Для того, чтобы узнать текущее состояние асинхронного адаптера, вы можете использовать следующую функцию:

/** *.Name aux_stat *.Title Определение режима асинхронного адаптера * *.Descr Эта функция считывает текущий режим * асинхронного порта и записывает его * в структуру с типом AUX_MODE. * *.Proto void aux_stat(AUX_MODE *mode, int port); * *.Params AUX_MODE mode - структура, описывающая * протокол и режим работы порта: * * typedef struct _AUX_MODE_ { * * union { * struct { * unsigned char len : 2, // длина символа * stop : 1, // число стоп-битов * parity : 2, // контроль четности * stuck_parity : 1, // фиксация четности * en_break_ctl : 1, // установка перерыва * dlab : 1; // загрузка регистра * // делителя * } ctl_word; * char ctl; * } ctl_aux; * * unsigned long baud; // скорость передачи данных * * } AUX_MODE; * * int port - номер асинхронного адаптера: * 0 - COM1, 1 - COM2 * *.Return Ничего * *.Sample aux_test.c **/


#include <stdio.h> #include <conio.h> #include "sysp.h"

void aux_stat(AUX_MODE *mode, int port) {

unsigned long b;

// Запоминаем режим адаптера

mode->ctl_aux.ctl = (char)inp(0x3fb - 0x100 * port);

// Устанавливаем старший бит режима // для считывания текушей скорости передачи

outp(0x3fb - 0x100 * port, mode->ctl_aux.ctl | 0x80);

// Считываем значение регистра делителя

b = inp(0x3f9 - 0x100 * port); b = b << 8; b += inp(0x3f8 - 0x100 * port);

// Преобразуем его в боды

switch (b) { case 1040: b = 110; break; case 768: b = 150; break; case 384: b = 300; break; case 192: b = 600; break; case 96: b = 1200; break; case 48: b = 2400; break; case 24: b = 4800; break; case 12: b = 9600; break; case 6: b = 19200; break; case 3: b = 38400; break; case 2: b = 57600; break; case 1: b = 115200; break; default: b=0; break; }

mode->baud = b;

// Восстанавливаем состояние адаптера

outp(0x3fb - 0x100 * port, mode->ctl_aux.ctl & 0x7f);

}

Прочитав состояние адаптера, вы можете изменить нужные вам поля в структуре AUX_MODE и вызвать функцию aux_init() для изменения параметров адаптера:

/** *.Name aux_init *.Title Инициализация асинхронного адаптера * *.Descr Эта функция инициализирует асинхронные * адаптеры, задавая протокол обмена данными * и скорость обмена данными. * *.Proto int aux_init(AUX_MODE *mode, int port, * int imask); * *.Params AUX_MODE *mode - указатель на структуру, * описывающую протокол и режим работы * порта; * * int port - номер асинхронного адаптера: * 0 - COM1, 1 - COM2 * * int imask - значение для регистра маски * прерываний * *.Return 0 - инициализация выполнена успешно; * 1 - ошибки в параметрах инициализации. * *.Sample aux_test.c **/

#include <stdio.h> #include <conio.h> #include "sysp.h"

int aux_init(AUX_MODE *mode, int port, int imask) {

unsigned div; char ctl;

// Вычисляем значение для делителя

switch (mode->baud) { case 110: div = 1040; break; case 150: div = 768; break; case 300: div = 384; break; case 600: div = 192; break; case 1200: div = 96; break; case 2400: div = 48; break; case 4800: div = 24; break; case 9600: div = 12; break; case 19200: div = 6; break; case 38400: div = 3; break; case 57600: div = 2; break; case 115200: div =1; break; default: return(-1); break; }

// Записываем значение делителя частоты

ctl = inp(0x3fb - 0x100 * port); outp(0x3fb - 0x100 * port, ctl | 0x80);

outp(0x3f9 - 0x100 * port, (div >> 8) & 0x00ff); outp(0x3f8 - 0x100 * port, div & 0x00ff);

// Записываем новое управляющее слово

outp(0x3fb - 0x100 * port, mode->ctl_aux.ctl & 0x7f);

// Устанавливаем регистр управления прерыванием

outp(0x3f9 - 0x100 * port, imask);

return(0);

}


Использование прерываний


Так как процесс последовательной передачи данных протекает достаточно медленно, имеет смысл выполнять его в фоновом режиме, используя прерывания по окончанию передачи или приема символа. Напомним, что порту COM1 соответствует аппаратное прерывание INT 0Ch, а COM2 - INT 0Bh.

Для разрешения прерываний необходимо установить в 1 биты порта управления прерываниями 3F9h, соответствующие тем прерываниям, которые мы желаем обрабатывать.

Когда произошло прерывание, программа-обработчик прерывания должна проанализировать причину прерывания, прочитав содержимое порта идентификации прерывания с адресом 3FAh.

Не забудьте, что в конце обработчика аппаратного прерывания должна находится последовательность команд:

mov al, 20h out 20h, al

iret

Может случиться так, что одновременно произойдет несколько прерываний. В этом случае бит 0 регистра идентификации прерывания будет установлен в 1. Если такая ситуация имеет место, перед завершением обработки прерывания вам надо снова прочитать регистр идентификации прерывания и обработать следующее прерывание. Так следует поступать до тех пор, пока бит 0 регистра идентификации прерывания не станет равным нулю.



Основные понятия и термины


Последовательная передача данных означает, что данные передаются с использованием единственной линии. При этом биты байта данных передается по очереди с использованием одного провода. Для синхронизации группе битов обычно предшествует специальный стартовый бит, после группы битов следуют бит проверки на четность и один или два стоповых бита. Иногда бит проверки на четность может отсуствовать.

Сказанное иллюстрируется следующим рисунком:

START 0 1 2 3 4 5 6 7 P STOP

-----¬ ----T---T---T---T---T---T---T---T---¬ ----- ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L---- L---+----

Из рисунка видно, что исходное состояние линии последовательной передачи данных - уровень логической 1. Стартовый бит START сигнализирует о начале передачи данных. Далее передаются биты данных, вначале младшие, затем старшие. Если используется бит четности P, то передается и он. Бит четности имеет такое значение, чтобы в пакете битов общее количество единиц (или нулей) было четно или нечетно.

В самом конце передаются один или два стоповых бита STOP, завершающих передачу байта. Затем уровень линии передачи снова устанавливается в 1 до прихода следующего стартового бита.

Использование четности, стартовых и стоповых битов определяют протокол передачи данных. Очевидно, что передатчик и приемник данных должны использовать один и тот же протокол, иначе связь будет невозможной.

Другая важная характеристика - скорость передачи данных. Она также должна быть одинаковой для передатчика и приемника.

Скорость передачи данных обычно измеряется в бодах. Боды - это количество передаваемых битов в секунду. При этом учитываются и старт/стопные биты, а также бит четности.

Иногда используется другой термин - биты в секунду (bps). Здесь имеется в виду эффективная скорость передачи данных, без учета служебных битов.



Передача данных


Перед записью байта данных в регистр передатчика необходимо убедиться в том, что регистр хранения передатчика свободен, то есть убедиться в том, что передача предыдущего символа завершена.

Признаком того, что регистр передатчика свободен, является установленный в 1 бит 5 регистра состояния линии с адресом 3FDh. Следующая функция ждет окончания передачи текущего символа, затем посылает в асинхронный адаптер следующий символ:

/** *.Name aux_outp *.Title Вывод символа в асинхронный адаптер * *.Descr Эта функция дожидается готовности * передатчика и посылает символ. * *.Proto void aux_outp(char chr, int port); * *.Params char chr - посылаемый символ; * * int port - номер асинхронного адаптера: * 0 - COM1, 1 - COM2 * *.Return Ничего * *.Sample aux_test.c **/

#include <stdio.h> #include <conio.h> #include "sysp.h"

void aux_outp(char chr, int port) {

unsigned status_reg, out_reg;

status_reg = 0x3fd - 0x100 * port; out_reg = status_reg - 5;

while( (inp(status_reg) & 0x20) == 0 );

outp(out_reg, chr);

}



Поддержка асинхронного адаптера в BIOS


Мы опишем функции BIOS, облегчающие обслуживание двух асинхронных адаптеров, COM1 и COM2. Эти функции доступны через прерывание INT14h.

Первая функция предназначена для инициализации портов асинхронного адаптера:

На входе: AH = 00h;

DX = номер порта: 0 - COM1, 1 - COM2;

AL = параметры инициализации (см. дальше).

На выходе: AH = состояние порта асинхронного адаптера;

AL = состояние модема.

При вызове этой функции регистр AL должен содержать параметры инициализации:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ LT+-+T+T+T+T+T+T- L=T=- L=¦ ¦ L=¦= Длина слова в битах: ¦ ¦ ¦ 00 - 5 бит; ¦ ¦ ¦ 01 - 6 бит; ¦ ¦ ¦ 10 - 7 бит; ¦ ¦ ¦ 11 - 8 бит. ¦ ¦ ¦ ¦ ¦ L===== Количество стоповых бит: ¦ ¦ 0 - 1 бит; ¦ ¦ 1 - 2 бита. ¦ ¦ ¦ L======= Четность: ¦ X0 - контроль на четность не ¦ используется; ¦ 01 - контроль на нечетность; ¦ 11 - контроль на четность. ¦ L============= Скорость передачи данных в бодах: 000 - 110 001 - 150 010 - 300 011 - 600 100 - 1200 101 - 2400 110 - 4800 111 - 9600

После вызова функции в регистр AH записывается состояние порта асинхронного адаптера:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ ¦ ¦ ¦ ¦ L= таймаут, если установлен этот бит, ¦ ¦ ¦ ¦ ¦ ¦ ¦ другие биты не имеют значения; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== регистр сдвига передатчика пуст; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== буферный регистр передатчика пуст; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= обнаружено состояние "BREAK"; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= ошибка синхронизации; ¦ ¦ ¦ ¦ ¦ L=========== ошибка четности; ¦ ¦ ¦ L============= ошибка переполнения входного регистра; ¦ L=============== данные готовы.

Регистр AL содержит байт состояния модема:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ ¦ ¦ ¦ ¦ L= линия CTS изменила состояние; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== линия DSR изменила состояние; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== линия RI изменила состояние; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= линия DCD изменила состояние; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= состояние линии CTS; ¦ ¦ ¦ ¦ ¦ L=========== состояние линии DSR; ¦ ¦ ¦ L============= состояние линии RI; ¦ L=============== состояние линии DCD.

Для передачи байта используется следующая функция:

На входе: AH = 01h;

DX = номер порта: 0 - COM1, 1 - COM2;

AL = передаваемый байт.

На выходе: AL сохраняется;

AH = состояние порта асинхронного адаптера, если бит 7 регистра AH установлен в 1, произошла ошибка.

Функция 02h предназначена для приема байта:

На входе: AH = 02h;

DX = номер порта: 0 - COM1, 1 - COM2.

На выходе: AL = принятый байт;

AH = состояние порта асинхронного адаптера, если регистр AH не равен 0, произошла ошибка.

Состояние порта асинхронного адаптера можно узнать с помощью функции 03h:

На входе: AH = 03h;

DX = номер порта: 0 - COM1, 1 - COM2.

На выходе: AH = состояние порта асинхронного адаптера;

AL = состояние модема.



Порт 3F8h.


Этот порт соответствует регистру передаваемых данных. Для передачи в порт 3F8h необходимо записать передаваемый байт данных. После приема данных от внешнего устройства они могут быть прочитаны из этого порта.

В зависимости от состояния старшего бита управляющего слова, выводимого в управляющий регистр с адресом 3FBh, назначение порта 3F8h может изменяться. Если этот бит равен 0, порт используется для записи передаваемых данных. Если же бит равен 1, порт используется для вывода значения младшего байта делителя частоты тактового генератора. Изменяя содержимое делителя, можно изменять скорость передачи данных. Старший байт делителя записывается в порт 3F9h.

Зависимость скорости передачи данных от значения делителя частоты представлено в следующей таблице:

Делитель Скорость передачи в бодах

1040 110 768 150 384 300 192 600 96 1200 48 2400 24 4800 12 9600 6 19200 3 38400 2 57600 1 115200



Порт 3F9h.


Порт используется либо как регистр управления прерываниями от асинхронного адаптера либо (после вывода в порт 3F9h байта с установленным в 1 старшим битом) для вывода значения старшего байта делителя частоты тактового генератора.

В режиме регистра управления прерываниями порт имеет следующий формат:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ LT+-+-+T+T+T+T+T- L==T==- ¦ ¦ ¦ L= 1 - разрешение прерывания при готовности ¦ ¦ ¦ ¦ принимаемых данных; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== 1 - разрешение прерывания после передачи ¦ ¦ ¦ байта (когда выходной буфер передачи ¦ ¦ ¦ пуст); ¦ ¦ ¦ ¦ ¦ L===== 1 - разрешение прерывания по обнаружению ¦ ¦ состояния "BREAK" или по ошибке; ¦ ¦ ¦ L======= 1 - разрешение прерывания по изменению ¦ состояния входных линий на разъеме ¦ RS-232-C (CTS, DSR, RI, DCD); ¦ L============ Не используются, должны быть равны 0.



Порт 3FAh.


Регистр идентификации прерывания. Считывая его содержимое, программа может определить причину прерывания.

Формат регистра:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ LT+-+-+-+T+T+T+T- L===T===- L=¦ L= 1 - Нет прерываний, ожидающих ¦ ¦ обслуживания. ¦ ¦ ¦ L=== 00 - Прерывание по линии состояния ¦ приемника, возникает при ¦ переполнении приемника, ошибках ¦ четности или формата данных ¦ или при состоянии "BREAK". ¦ Сбрасывается после чтения состояния ¦ линии из порта 3FDh. ¦ ¦ 01 - Данные приняты и доступны для чтения. ¦ Сбрасывается после чтения данных ¦ из порта 3F8h. ¦ ¦ 10 - Буфер передатчика пуст. Сбрасывается ¦ при записи новых данных в регистр ¦ данных передатчика, порт 3F8h. ¦ ¦ 11 - Состояние модема. Устанавливается при ¦ изменении состояния входных линий ¦ CTS, RI, DCD, DSR. Сбрасывается ¦ после чтения состояния модема из ¦ порта 3FEh. ¦ L========== Должно быть равно 0.



Порт 3FBh.


Управляющий регистр, доступен по записи и чтению.

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ L=¦ ¦ L=¦= Длина слова в битах: ¦ ¦ ¦ ¦ ¦ 00 - 5 бит; ¦ ¦ ¦ ¦ ¦ 01 - 6 бит; ¦ ¦ ¦ ¦ ¦ 10 - 7 бит; ¦ ¦ ¦ ¦ ¦ 11 - 8 бит. ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== Количество стоповых бит: ¦ ¦ ¦ ¦ 0 - 1 бит; ¦ ¦ ¦ ¦ 1 - 2 бита. ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= Четность: ¦ ¦ ¦ X0 - контроль на четность не ¦ ¦ ¦ используется; ¦ ¦ ¦ 01 - контроль на нечетность; ¦ ¦ ¦ 11 - контроль на четность. ¦ ¦ ¦ ¦ ¦ L=========== Фиксация четности. При установке этого ¦ ¦ бита бит четности всегда принимает ¦ ¦ значение 0 (если биты 3-4 равны 11) или 1 ¦ ¦ (если биты 3-4 равны 01). ¦ ¦ ¦ L============= Установка перерыва. Вызывает вывод ¦ строки нулей в качестве сигнала ¦ "BREAK" для подключенного устройства. ¦ L=============== 1 - порты 3F8h и 3F9h используются для для загрузки делителя частоты тактового генератора; 0 - порты используются как обычно.



Порт 3FCh.


Регистр управления модемом. Управляет состоянием выходных линий DTR, RTS , линий, специфических для модемов OUT1 и OUT2, для запуска диагностики соединенных вместе замкнутых входе и выходе асинхронного адаптера.

Формат порта:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+-+T+T+T+T+T+T- L=T=- ¦ ¦ ¦ ¦ L= линия DTR; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== линия RTS; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== линия OUT1 (запасная); ¦ ¦ ¦ ¦ ¦ L======= линия OUT2 (запасная); ¦ ¦ ¦ L========= запуск диагностики при входе ¦ асинхронного адаптера, замкнутом ¦ на его выход; ¦ L============= должно быть равно 0.



Порт 3FDh.


Регистр состояния линии.

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ ¦ ¦ ¦ ¦ L= Данные получены и готовы для чтения, ¦ ¦ ¦ ¦ ¦ ¦ ¦ сбрасывается при чтении данных. ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== Ошибка переполнения. Был принят новый ¦ ¦ ¦ ¦ ¦ ¦ байт данных, а предыдущий еще не был ¦ ¦ ¦ ¦ ¦ ¦ считан программой. Предыдущий байт ¦ ¦ ¦ ¦ ¦ ¦ потерян. ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== Ошибка четности, сбрасывается после ¦ ¦ ¦ ¦ ¦ чтения состояния линии. ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= Ошибка синхронизации. ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= Обнаружен запрос на прерывание ¦ ¦ ¦ передачи "BREAK" - длинная строка нулей. ¦ ¦ ¦ ¦ ¦ L=========== Регистр хранения передатчика пуст, в него ¦ ¦ можно записывать новый байт для передачи. ¦ ¦ ¦ L============= Регистр сдвига передатчика пуст. Этот ¦ регистр получает данные из регистра ¦ хранения и преобразует их в ¦ последовательный вид для передачи. ¦ L=============== Таймаут (устройство не связано с компьютером).



Порт 3FEh.


Регистр состояния модема.

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ ¦ ¦ ¦ ¦ L= линия CTS изменила состояние ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== линия DSR изменила состояние ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== линия RI изменила состояние ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= линия DCD изменила состояние ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= состояние линии CTS ¦ ¦ ¦ ¦ ¦ L=========== состояние линии DSR ¦ ¦ ¦ L============= состояние линии RI ¦ L=============== состояние линии DCD



ПОРТ ПОСЛЕДОВАТЕЛЬНОЙ ПЕРЕДАЧИ ДАННЫХ


6.1.

6.2.

6.3.

6.4.

6.5.

Эта глава посвящена порту последовательной передачи данных. Его называют еще портом RS-232-C, или асинхронным адаптером RS-232-C. Компьютер IBM PC поддерживает интерфейс RS-232-C не в полной мере, скорее разъем, обозначенный на корпусе компьютера как порт последовательной передачи данных, содержит некоторые из сигналов, входящих в интерфейс RS-232-C и имеющих соответствующие этому стандарту уровни напряжения.

В настоящее время порт последовательной передачи данных используется очень широко. Вот далеко не полный список применений:

подключение мыши;

подключение графопостроителей (плоттеров), сканеров, принтеров, диджитайзеров;

связь двух компьютеров через порты последовательной передачи данных с использованием специального кабеля и таких программ, как FastWire II или Norton Commander;

подключение модемов для передачи данных по телефонным линиям;

подключение к сети персональных компьютеров.

Практически каждый компьютер оборудован хотя бы одним портом для последовательной передачи данных.



Порты асинхронного адаптера


На этапе инициализации системы модуль POST BIOS тестирует имеющиеся асинхронные адаптеры и инициализирует первые два. Их базовые адреса располагаются в области данных BIOS начиная с адреса 0000:0400h.

Первый адаптер COM1 имеет базовый адрес 3F8h и занимает диапазон адресов от 3F8h до 3FFh. Второй адаптер COM2 имеет базовый адрес 2F8h и занимает адреса 2F8h...2FFh.

Асинхронные адаптеры могут вырабатывать прерывания:

COM1 - IRQ4 (соответствует INT 0Ch)

COM2 - IRQ3 (соответствует INT 0Bh)

Рассмотрим назначение отдельных битов этих портов.



Прием данных


Аналогично передаче данных, перед вводом символа из порта приемника 3F8h необходимо убедиться в том, что бит 0 порта 3FDh установлен в 1. Это означает, что символ принят из линии и находится в буферном регистре приемника.

Для приема данных мы подготовили следующую функцию:

/** *.Name aux_inp *.Title Ввод символа из асинхронного адаптера * *.Descr Эта функция дожидается готовности * приемника и вводит символ из асинхронного * адаптера. * *.Proto char aux_inp(int port); * *.Params int port - номер асинхронного адаптера: * 0 - COM1, 1 - COM2 * *.Return Принятый символ * *.Sample aux_test.c **/

#include <stdio.h> #include <conio.h> #include "sysp.h"

char aux_inp(int port) {

unsigned status_reg, inp_reg;

status_reg = 0x3fd - 0x100 * port; inp_reg = status_reg - 5;

while( (inp(status_reg) & 1) == 0 );

return(inp(inp_reg));

}



Пример программы передачи данных


Приведем пример программы, использующей описанные выше функции для изменения скорости передачи данных и для проверки асинхронного адаптера. Для правильной работы программы выход асинхронного адаптера должен быть соединен с его входом.

// Программа работает с асинхронным адаптером COM1. // Для правильной работы необходимо замкнуть // вместе контакты 2 и 3 разъема COM1.

#include <stdio.h> #include <conio.h> #include "sysp.h"

void main(void); void main(void) {

AUX_MODE amd;

aux_stat(&amd, 0); printf("\nСостояние порта COM1:" "\nКод длины символа: %d" "\nКод числа стоп-битов: %d" "\nКонтроль четности: %d" "\nСкорость передачи: %lu", amd.ctl_aux.ctl_word.len, amd.ctl_aux.ctl_word.stop, amd.ctl_aux.ctl_word.parity, (unsigned long)amd.baud);

amd.baud = 115200;

aux_init(&amd, 0, 0);

aux_stat(&amd, 0); printf("\nСостояние порта COM1:" "\nКод длины символа: %d" "\nКод числа стоп-битов: %d" "\nКонтроль четности: %d" "\nСкорость передачи: %lu", amd.ctl_aux.ctl_word.len, amd.ctl_aux.ctl_word.stop, amd.ctl_aux.ctl_word.parity, (unsigned long)amd.baud);

printf("\n\nТестирование асинхронного адаптера." "\nНажимайте клавиши!" "\nДля завершения работы нажмите CTRL-C" "\n");

for(;;) {

// Вводим символ с клавиатуры и передаем его // в асинхронный адаптер

aux_outp(getch(), 0);

// Вводим символ из асинхронного адаптера и // отображаем его на экране

putchar(aux_inp(0)); } }



Программирование асинхронного адаптера


К сожалению, MS-DOS не содержит сколько-нибудь серьезной поддержки асинхронного адаптера. Две функции прерывания INT 21h с номерами 3 и 4 предназначены для чтения и записи байтов через асинхронный адаптер. Обе эти функции имеют дело с адаптером COM1 или AUX. Функция 3 получает в регистре AL символ, принятый из адаптера, функция 4 посылает в адаптер символ, записанный в регистр DL.

Основной недостаток функций MS-DOS, предназначенных для работы с адаптером, заключается в отсуствии их функциональной полноты. Используя только функции MS-DOS, вы не сможете проанализировать ошибочные ситуации и изменить режим работы асинхронного адаптера - нет соответствующих средств.

Функции BIOS, обслуживающие адаптер, более разнообразны. Однако и им присущи недостатки. Например, вы не сможете установить скорость передачи более 9600 бод или использовать режим фиксации четности. Нет возможности узнать текущий режим асинхронного адаптера, отсутствует поддержка модема.

Поэтому для программирования асинхронного адаптера мы рекомендуем использовать порты ввода/вывода микросхемы 8250.

6.5.1.

6.5.2.

6.5.3.

6.5.4.

6.5.5.



ESC "@" Инициализация принтера


Для сброса принтера в исходное состояние программа должна послать на принтер два байта - байт ESC (1Bh) и байт, соответствующий ASCII-символу "@" (40h).



ESC "x" n Выбор качества печати:


0 - низкое качество; 1 - качественный шрифт NLQ.

Для задания типа шрифта надо вывести на принтер три байта: символ ESC (1Bh), символ "x" (78h), затем код шрифта (30h...31h).

Существуют различные команды, позволяющие определить размер межстрочного интервала, расположение левой и правой границ листа, используемый для печати шрифт. Можно выполнять печать графических изображений, о чем мы раскажем немного позже.

Если вас не устраивает шрифт, который записан в ПЗУ принтера (например, в нем нет русских букв), вы можете использовать команды для загрузки собственного шрифта.

Приведем пример программы, которая посылает в принтер командные последовательности и обычные символы, пользуясь функцией 05h прерывания INT21h:

#include <dos.h> #include <stdio.h>

main() {

char buffer[] = {

0x1b, '@', // Сбрасываем принтер в исходное // состояние.

7,7,7, // Выдаем 3 раза звуковой сигнал.

0x1b, 'x','0', // Устанавливаем низкое // качество печати.

'S','t','r','i','n','g',' ','1', // Печатаем // строку.

0x1b, 'x','1', // Устанавливаем высокое // качество печати.

'S','t','r','i','n','g',' ','2', // Печатаем // строку.

0x0a, // Переводим строку.

7,7,7, // Выдаем 3 раза звуковой сигнал.

0 };

char *p;

// Выводим строку символов на принтер

for(p = buffer; *p != 0; p++) bdos(0x05, *p, 0); }

Для вывода символа на принтер через функцию MS-DOS здесь использована функция bdos(), входящая в состав стандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0. Первый параметр функции bdos() - номер выполняемой функции прерывания MS-DOS INT 21h, второй - содержимое регистра DX перед вызовом этой функции, и третий - содержимое регистра AL.

В комментариях к программе объясняется назначение управляющих последовательностей, посылаемых на принтер. Перед запуском программы необходимо убедиться в том, что принтер включен и находится в состоянии ON LINE, иначе программа перейдет в состояние ожидания.



Генерация звукового сигнала


Если послать этот байт, принтер издаст звуковой сигнал. Сигнал удобно использовать для привлечения внимания оператора, например, когда кончилась бумага.



H Перевод страницы


Принтер распечатывает все символы, находившиеся в буфере, затем выполняет прогон одного листа бумаги.



H Перевод строки


Когда этот символ посылается на принтер, все символы, находящиеся во внутреннем буфере принтера, распечатываются, затем каретка возвращается к началу строки и происходит подача листа вперед на одну строку.



H Возврат каретки


Распечатываются все символы из буфера принтера, затем каретка (печатающая головка) возвращается к началу строки. В зависимости от переключателя конфигурации SW 2-4 может дополнительно выполняться прогон бумаги на одну строку.



Печать русских букв


Если среди национальных наборов символов, имеющихся в постоянном запоминающем устройстве, имеются русские буквы, то вам достаточно правильно установить переключатели SW 1-6...1-8 и SW 1-1.

Если же набора русских символов нет, или в ПЗУ принтера использована не та кодировка русских символов, вам потребуются специальные программы загрузки шрифтов, такие как SETPR, комплекс SOLO, LOADFONT или аналогичные. Все эти программы используют специальные командные последовательности для переопределения тех символов, коды которых соответствуют русским буквам.

Для разработки собственных символов используется сетка. В 9-игольчатых принтерах эта сетка имеет 11 столбцов и девять строк:

Из девяти строк может использоваться только 8 верхних или 8 нижних (это показано на левом и правом рисунках соответственно).

Обычно символ располагается выше утолщенной линии, то есть в строках с номерами от 1 до 7. Исключение составляют такие буквы, как "у", "ц" и т.п. Нижние "хвостики" этих букв должны находиться на строке с номером 0.

Есть ограничение на расположение отдельных точек определяемого символа в строке - эти точки не должны находиться рядом, то есть между точками в строке должна находиться одна свободная ячейка.

Для переопределения символов 9-игольчатый принтер Epson использует команду ESC "&":

ESC "&" "0" n1 n2 a1 d1 d2 ... dn Определить символы

Параметры n1 и n2 задают диапазон кодов ASCII символов, начертание которых необходимо переопределить. Если вы переопределяете только один символ, эти два параметра должны быть одинаковыми.

Параметр a1 определяет ширину символа в точках и его положение в сетке (использует ли символ верхние восемь линий, либо нижние восемь линий). Ширина определяемого символа требуется для печати в пропорциональном режиме, когда место, занимаемое каждой буквой в строке распечатки, зависит от ее ширины. Например, буква "Ш" шире, чем буква "И".

Старший бит параметра a1 задает расположение символа в сетке. Если этот бит равен 1, используются восемь верхних линий сетки, если 0 - восемь нижних.


Младшие семь битов задают ширину символа и представляют собой число, определяемое по следующей схеме:

возмите в качестве начального значения для ширины символа число 8;

для каждого пустого столбца в сетке с правой стороны символа надо вычесть из начального значения единицу;

для каждого пустого столбца в сетке с левой стороны символа надо прибавить к начальному значению число 16.

Пусть определяемый символ располагается в верхней части сетки (использует восемь верхних строк). Пусть этот символ начинается в третьем столбце и заканчивается в 7 столбце. Тогда десятичное значение параметра a1 вычисляется следующим образом:

a1 = 8(начальное значение) - - 2(два пустых столбца справа) + + 32(два пустых столбца слева) + + 128(старший бит равен 1) = 166

Если ваш символ использует верхние восемь строк сетки, начинается в первом столбце и заканчивается в девятом, в качестве параметра a1 подходит значение 136. При этом символы будут печататься верхними восемью иголками печатающей головки. Для использования нижних восьми иголок и такой же ширины символа задайте значение a1 равное 8.

Параметры d1...dn - образцы столбцов точек для определяемого символа. Их должно быть всегда 11, даже если символ содержит пустые столбцы. Для пустых столбцов в качестве образца надо задать 0.

Для включения определенного программой набора символов в работу необходимо выдать команду ESC "%" "0", для использования набора символов из внутреннего ПЗУ принтера выдайте команду ESC "%" "1".

Приведем пример программы, изменяющей начертание символа "@" в принтере Epson FX-850/1050. Для правильной работы программы переключатель SW 1-1 должен быть установлен в положение OFF.

#include <dos.h> #include <stdio.h>

main() {

char buffer[] = {

0x1b, '@', // Сбрасываем принтер в исходное // состояние.

7,7,7, // Выдаем 3 раза звуковой сигнал.

// Определяем вместо "@" новый символ:

0x1b, '&', 0,

'@', '@', 136, 32,80,168,84,42,84,168,80,32,0,0,



// Выдаем строку символов, используем начертание, // заданное в ПЗУ принтера.

'@', '@', '@', '@', '@', 0x0a,

// Используем новое начертание:

0x1b, '%', 1, '@', '@', '@', '@', '@', 0x0a,

// Возвращаемся опять к старому начертанию:

0x1b, '%', 0, '@', '@', '@', '@', '@', 0x0a,

7,7,7, // Выдаем 3 раза звуковой сигнал.

'$' // Признак конца массива данных

};

char *p;

// Выводим строку символов на принтер

for(p = buffer; *p != '$'; p++) bdos(0x05, *p, 0); }

Рассмотрим теперь метод переопределения начертания символов в 24-иголочном принтере Epson LQ-2550. Для определения начертания символов в этом принтере используется сетка высотой 24 точки - соответственно, по одной точке для каждой иголки в печатающей головке.

24-иголочный принтер использует несколько наборов символов:

черновой набор символов (Draft);

качественный набор символов (Letter Quality);

пропорциональный набор символов (Proportional).

В зависимости от используемого набора символов ширина сетки может быть либо 9 точек (для чернового набора символов, либо 29 точек (для качественного набора символов), либо 37 точек (для пропорционального набора символов). Кроме того, для последних двух наборов столбцы сетки расположены ближе друг к другу, чем для чернового набора.

На рисунке показаны сетки для чернового и качественного/пропорционального наборов символов принтера Epson LQ-2550:



Обычные символы располагаются между жирными линиями. Нижние линии сетки используются для подчеркивания и изображения "хвостиков" таких букв, как "у", "ц" и т.п.

Так же, как и для 9-игольчатых принтеров, существует ограничение на расположение точек в узлах сетки: справа и слева от каждой точки должны располагаться пустые позиции.

Приведем формат команды для переопределения символов в принтере Epson LQ-2550:

ESC "&" "0" n1 n2 d0 d1 d2 data Определить символы

Параметры n1 и n2 задают диапазон кодов ASCII символов, начертание которых необходимо переопределить. Их назначение такое же, как и для 9-игольчатых принтеров Epson. Если вы переопределяете только один символ, эти два параметра должны быть одинаковыми.



Далее следуют три байта данных, которые задают ширину символа и размер свободного пространства вокруг символа. Параметр d0 задает количество свободных столбцов слева от символа, параметр d2 определяет количество свободных столбцов справа от символа. Параметр d1 определяет ширину символа в столбцах сетки.

Изменяя ширину символа и размер свободного пространства вокруг него можно формировать пропорциональные наборы символов.

В следующей таблице приведены максимальные значения для параметров d0, d1, d2 для различных наборов символов:

Набор d1 d0+d1+d2

Черновой 9 12

Качественный, 29 36 10 символов на дюйм

Качественный, 23 30 12 символов на дюйм

Пропорциональный 37 42

После параметра d2 следует последовательность байтов, описывающих символ, т.е. образец для символа. Для задания одного столбца сетки требуется три байта, поэтому для определения одного символа вы должны задать (d1 * 3) байтов данных.


Печать в графическом режиме


До сих пор мы использовали принтер только для печати символьной информации - буквы, цифры, символы псевдографики и т.д. Коды символов мы тем или иным способом выводили на принтер, принтер распечатывал их с использованием внутренних шрифтов, записанных в ПЗУ принтера, или с использованием загружаемых шрифтов.

Однако печать символов - это не все, на что способен матричный принтер. Вспомним, как происходит печать букв.

Принтерная головка содержит 9 или 24 иголки, расположенных вертикально. Печатающая головка движется в горизонтальном направлении вдоль листа бумаги, причем между головкой и листом находится красящая лента. Каждая иголка управляется отдельным электромагнитом, при включении которого она "выстреливается" в направлении красящей ленты, оставляя на бумаге маленькую точку. Цвет этой точки определяется красящей лентой. Цветные матричные принтеры (например, LQ2550) заряжаются многоцветной лентой. Эта лента напоминает черно-красную ленту для механических пишущих машинок, но на ней больше цветов. Обычно используются четырехцветные ленты, раскрашенные следующими цветами - желтый, красный, синий, черный.

В процессе печати кассета с цветной лентой может перемещаться в вертикальном направлении, поэтому перед печатающей головкой может оказаться участок ленты с одним из четырех цветов.

При наложении цветов возможно получение различных цветовых комбинаций, поэтому хотя цветов на ленте всего четыре, цветной принтер может печатать достаточно качественные и многоцветные изображения.

Когда принтер печатает алфавитно-цифровой символ, он, передвигая печатающую головку слева направо (или справа налево, что тоже возможно), "выстреливает" теми иголками, которые соответствуют заданному шрифтом изображению символа.

Очевидно, что если бы у нас была возможность самостоятельно управлять иголками и перемещением кассеты с цветной красящей лентой, мы могли бы самостоятельно формировать цветное графическое изображение, так, как это делается в полиграфии. Если вы внимательно посмотрите на фотографии, напечатанные в газетах или журналах, вы заметите, что графическое изображение состоит из отдельных точек разного цвета и очень маленького размера.


Практически любые матричные принтеры позволяют печатать графические изображения. Девяти иголочные принтеры Epson FX используют для графической печати 8 верхних иголок. При этом за один проход можно напечатать одну графическую "строку", послав в принтер битовый образ строки.

Если печатаемое графическое изображение по высоте превышает 8 точек, оно выводится в несколько приемов, построчно. После вывода очередной строки программа должна продвинуть бумагу на одну строку вперед. Для того чтобы между графическими строками не оставалось свободного места, необходимо правильно установить межстрочный интервал - не более 8/72 дюйма.

Как перевести принтер в режим графической печати?

Для этого принтер Epson FX-1050 использует следующую команду:

ESC "*" m n1 n2 data Печать в графическом режиме

В этой команде m задает режим печати:

Значение m Режим

0 Одинарная плотность, 60 точек на дюйм

1 Двойная плотность, 120 точек на дюйм

2 Двойная плотность, печать с высокой скоростью, 120 точек на дюйм

3 Учетверенная плотность, 240 точек на дюйм

4 Режим CRT I, плотность 80 точек на дюйм

5 Режим плоттера (1:1), плотность 72 точки на дюйм

6 Режим CRT II, плотность 90 точек на дюйм

7 Режим плоттера с двойной плотностью, 144 точки на дюйм

Параметры n1 и n2 определяют длину печатаемой графической строки в точках. При определении длины графической строки необходимо учитывать, что в режиме одинарной плотности на строке длиной 8 дюймов можно разместить 480 точек, в режиме учетверенной плотности - около 2000. 

Так как передача данных в принтер выполняется по байтам, для представления длины строки приходится использовать два байта информации. Для вычисления параметров n1 и n2 можно использоваться следующей схемой действий:

делим длину строки на 256, целочисленный результат деления используем в качестве параметра n2;

остаток от деления используем в качестве n1.

Например, пусть нам надо распечатать строку из 1234 точек. Тогда параметр n2 будет равен 1234 / 256 = 4. Остаток от деления составит 1234 - 256 * 4 = 210. Это и есть параметр n1.



Проверяем: 4 * 256 + 210 = 1234

Команда должна всегда содержать два параметра, даже если параметр n2 получился равным нулю.

Вслед за параметрами n1 и n2 должны следовать байты графических данных, предназначенные для печати. Должно быть передано точно n2 * 256 + n1 байтов.

Если вы передатите меньше графических данных, чем это было определено в команде ESC "*", следующие вводимые в принтер команды или данные будут интерпретироваться как графические данные. Если вы передадите больше графических данных чем нужно, лишние данные будут напечатаны как обычный текст.

Для представления одного восьмиточечного столбца графической строки используется один байт данных, причем верхней точке в столбце соответствует старший разряд байта, а нижней - младший:

7 Этому столбцу соответствует o 6 байт 10011011b или 9Bh o 5 * 4 * 3 o 2 * 1 * 0

На этом рисунке "*" означает точку в столбце графической строки, "o" - отсуствие точки.

Вам необходимо подготовить таким образом байты для всех столбцов, входящих в распечатываемую графическую строку.

Цветной 24-игольчатый принтер Epson LQ-2550 может работать в описанном выше 8-битовом графическом режиме. Это сделано для обеспечения совместимости со старым программным обеспечением, рассчитанным на принтеры серий FX, RX, LX и EX. Однако все возможности этого принтера раскрываются только при использовании всех его 24 иголок. В этом случае графическое изображение печатается отдельными строчками, высота которых составляет 24 точки. При этом для представления одного столбца графической строки требуется три байта данных. Каждый байт должен готовиться отдельно, при этом можно считать, что 24-битовая графическая строка состоит из трех 8-битовых.

Формат команды графической печати для этого принтера расширен по сравнению с описанным выше:

ESC "*" m n1 n2 data Печать в графическом режиме

В этой команде m, как и раньше, задает режим печати. Однако для этого параметра определено больше значений:

Значение m Режим



0 Одинарная плотность, 60 точек на дюйм, 8-битовая графика

1 Двойная плотность, 120 точек на дюйм, 8-битовая графика

2 Двойная плотность, печать с высокой, скоростью, 120 точек на дюйм, 8-битовая графика

3 Учетверенная плотность, 240 точек на дюйм, 8-битовая графика

4 Режим CRT I, плотность 80 точек на дюйм, 8-битовая графика

6 Режим CRT II, плотность 90 точек на дюйм, 8-битовая графика

32 Одинарная плотность, 60 точек на дюйм, 24-битовая графика

33 Двойная плотность, 120 точек на дюйм, 24-битовая графика

38 Режим CRT III, плотность 90 точек на дюйм, 24-битовая графика

39 Тройная плотность, 180 точек на дюйм, 24-битовая графика

40 Шестикратное увеличение плотности, 360 точек на дюйм, 24-битовая графика

Учтите, что для команды 24-битового графического вывода требуется массив графических данных в три раза больше по размеру, чем для 8-битовой команды.

Приведем пример программы, выводящей на принтер в режиме 8-битовой графики строку, состоящую из 40 столбцов:

#include <dos.h> #include <stdio.h>

union REGS rg;

int main() {

int i;

// Переводим строку

printchar(0x0d); printchar(0x0a);

// Выдаем на принтер команду графической // печати (ESC "*" m n1 n2 data)

printchar(27); printchar('*');

// Выбираем режим 0 - 8-битовая графика, // одинарная плотность

printchar(0);

// Задаем длину графической строки: // n1 = 40; n2 = 0

printchar(40); printchar(0);

// Выводим в цикле 40 раз байт DBh

for(i=0; i<40; i++) printchar(0xdb);

// Переводим строку

printchar(0x0d); printchar(0x0a);

}

// ------------------------------------ // Эта функция выводит один символ // на стандартный принтер (LPT1) // ------------------------------------

int printchar(int chr) {

// Вызываем функцию 5 прерывания INT 21h - // распечатка символа на принтере.

rg.h.ah = 5; rg.h.dl = chr;

int86(0x21, &rg, &rg); }

Аналогичная программа, использующая 24-битовую графику на принтере Epson LQ-2550:

#include <dos.h> #include <stdio.h>

union REGS rg;



int main() {

int i;

// Переводим строку

printchar(0x0d); printchar(0x0a);

// Выдаем на принтер команду графической // печати (ESC "*" m n1 n2 data)

printchar(27); printchar('*');

// Выбираем режим 32 - 24-битовая графика, // одинарная плотность

printchar(32);

// Задаем длину графической строки: // n1 = 40; n2 = 0

printchar(40); printchar(0);

// Выводим в цикле 120 раз байт DBh. Для вывода // строки из 40 столбцов в 24-битовом режиме // требуется в три раза больше графических данных, // чем для 8-битового режима.

for(i=0; i<120; i++) printchar(0xdb);

// Переводим строку

printchar(0x0d); printchar(0x0a);

}

// ------------------------------------ // Эта функция выводит один символ // на стандартный принтер (LPT1) // ------------------------------------

int printchar(int chr) {

// Вызываем функцию 5 прерывания INT 21h - // распечатка символа на принтере.

rg.h.ah = 5; rg.h.dl = chr;

int86(0x21, &rg, &rg);

}

Для вывода на принтер сложных графических изображений ваша программа должна сначала подготовить массив данных для построчной 8-битовой или 24-битовой печати. Затем готовый массив можно вывести на принтер, используя несколько команд графического вывода (по одной команде на одну графическую строку).


Подключение принтера к компьютеру


Принтер подключается к компьютеру двумя способами: либо через асинхронный адаптер, рассмотренный ранее, либо через порт параллельной передачи данных. Возможно подключение к одному компьютеру сразу нескольких принтеров, причем принтеры могут быть подключены одновременно и к параллельному порту, и к асинхронному последовательному адаптеру.

Для подключения принтера к последовательному порту компьютера принтер должен быть оборудован специальным последовательным интерфейсом. Кроме того, необходимо использовать специальный кабель. Если вы подсоедините принтер к последовательному порту при помощи кабеля, предназначенного для работы с параллельным портом, то это может привести к повреждениям в оборудовании компьютера или принтера. Внимательно читайте раздел документации на принтер, посвященный подключению его к компьютеру.

В этой главе мы сделаем упор на использование порта параллельной передачи данных, так как именно этот вариант подключения принтера наиболее распространен.



Порт 37Ah.


Порт управления принтером, доступен для чтения и записи:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+-+T+T+T+T+T+T- L=T=- ¦ ¦ ¦ ¦ L= строб данных, принимает значение 1 при ¦ ¦ ¦ ¦ ¦ выводе байта, подключен к 1 контакту ¦ ¦ ¦ ¦ ¦ разъема, STROBE; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== автоматический перевод строки после ¦ ¦ ¦ ¦ символа "возврат каретки" CR, контакт 14, ¦ ¦ ¦ ¦ AUTO LineFeed; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== сброс принтера, активный уровень - 0, ¦ ¦ ¦ контакт 16, INIT; ¦ ¦ ¦ ¦ ¦ L======= выбор принтера для работы, контакт 17, ¦ ¦ SLCT IN; ¦ ¦ ¦ L========= разрешение прерывания от принтера, ¦ IRQ Enable; ¦ L============= равно 0.

Если прерывания от принтера разрешены, они вырабатываются, когда сигнал готовности принтера ACK (контакт разъема10) принимает уровень логического 0.



Порт 378h.


Этот порт предназначен для записи выводимого на принтер байта данных. Возможно также чтение только что записанного байта.



Порт 379h.


Порт состояния принтера, доступен только для чтения:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+-+T- ¦ ¦ ¦ ¦ ¦ L===¦= установлены в 0; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= сигнал ошибки, активный уровень - 0, ¦ ¦ ¦ ¦ контакт разъема - 15, ERROR; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= принтер выбран, контакт 13, SLCT; ¦ ¦ ¦ ¦ ¦ L=========== конец бумаги, контакт 12, PE; ¦ ¦ ¦ L============= готовность принтера, активный уровень - 0, ¦ контакт разъема - 10, ACK; ¦ L=============== 0 - принтер занят, находится в состоянии offline или произошла ошибка, контакт 11, BUSY.

Обычно редко приходится работать с принтером на уровне портов ввода/вывода, так как достаточно использовать функции BIOS или MS-DOS, предназначенные для этого. Приведенная выше информация может пригодиться вам для разработки собственного драйвера принтера или для подключения к принтерному порту какого-либо другого устройства ввода/вывода, например, аналого-цифрового преобразователя.

Для тех, кто будет использовать принтерный порт для подключения аппаратуры, приведем таблицу назначения контактов разъемов принтерного порта (контакт PC) на компьютере и контактов разъема непосредственно на принтере (контакт принтера):

Контакт Контакт Назначение Вход/выход PC принтера

1 1 Строб (STROBE) Выход, инверсия

2 2 Данные, бит 0 Выход

3 3 Данные, бит 1 Выход

4 4 Данные, бит 2 Выход

5 5 Данные, бит 3 Выход

6 6 Данные, бит 4 Выход

7 7 Данные, бит 5 Выход

8 8 Данные, бит 6 Выход

9 9 Данные, бит 7 Выход

10 10 Подтверждение, ACK Вход, инверсия

11 11 Занятость, BUSY Вход

12 12 Конец бумаги, PE Вход

13 13 Выбор, SLCT Вход

14 14 Авт. перевод Выход, инверсия строки, Auto Line Feed

15 32 Ошибка, ERROR Вход, инверсия

16 31 Сброс принтера, Выход, инверсия INIT

17 36 Принтер выбран, Выход, инверсия SLCT IN

18-25 16,17, Земля - 19-30,33

Для сигналов, отмеченных в таблице словом "инверсия", используется уровень логического нуля в активном состоянии сигнала.

Используя принтерный порт для управления внешними устройствами, будьте осторожны и выполняйте все правила заземления устройств. Если ваш устройство не заземлено или заземлено неправильно, принтерный порт может выйти из строя.


Следует также учитывать, что нагрузка на выходную линию принтерного порта не должна превышать одного входа TTL.

Если по каким-либо причинам вы пожелаете работать с принтером через порты ввода/вывода, вам необходимо изучить временную диаграмму принтерного порта. Она представлена на следующем рисунке:

----------------------------------¬ ¦ ¦ BUSY --------- L-------- ------ ACK ---------------------------------------¬ ---- ¦ ¦ L--------- --------------¬ ¦ ¦ DATA ----- L-------------------------------- ------ STROBE ------¬ ------------------------------------ ¦ ¦ L---------

Для того, чтобы вывести символ на принтер, программа вначале должна убедится, что уровень сигнала на линии BUSY (бит 7 порта 379h) равен 0, а уровень сигнала на линии ACK (бит 6 порта 379h) - единице. После этого следует установить код выводимого символа на линиях DATA (порт 378h).

Затем не ранее, чем через 0,5 мкс линию STROBE (бит 0 порта 37Ah) необходимо перевести в состояние логического 0. При этом выводимый символ запишется во внутренний буфер принтера. Уровень логического нуля необходимо удерживать в течение как минимум 0,5 мкс. Это время нужно для того, чтобы символ записался в буфер принтера. После истечения интервала времени линию STROBE нужно опять перевести в состояние логической единицы.

После того, как программа установит линию STROBE в состояние логического нуля, выходная линия принтера BUSY устаналвивается в единицу, сигнализируя о том, что принтер занять обработкой полученного символа и временно не может принимать другие символы.

Когда принтер полность обработает выведенный символ, линия ACK перейдет в состояние 0. Приблизительно через 5 мкс после этого линия BUSY также перейдет в состояние 0.

Еще через 5 мкс линия ACK примет состояние 1. Теперь принтер готов принят следующий символ распечатываемых данных.


ПРИНТЕР


7.1.

7.2.

7.3.

7.4.

7.5.

7.6.

7.7.

7.8.

В этой главе мы расскажем об использовании матричных принтеров и способах их подключения к компьютеру.

Матричные принтеры распространены наиболее широко, в основном из-за их низкой стоимости. Если у вас нет потребности в печати большого объема документации (тысячи листов), или вам не нужно типографское качество получаемых документов, используйте матричные принтеры.

Существуют две группы матричных принтеров, различающихся по системе используемых команд - это принтеры, совместимые с принтерами Epson и принтеры, совместимые с IBM Proprinter. Принтеры некоторых третьих фирм-производителей компьютерного оборудования (не Epson и не IBM) обычно выполняют эмуляцию команд обоих или одной из этих групп в зависимости от установки переключателей конфигурации.

Кроме того, различные модели принтеров, например, производства фирмы Epson, также отличаются набором команд. Как правило, обеспечивается совместимость по командам снизу вверх, то есть более новые модели поддерживают команды предыдущих моделей принтеров.

Очень распространены принтеры серии Epson FX: FX-80, FX-850, FX-1050. Печатающие головки этих принтеров имеют девять иголок, поэтому качество печати принтеров серии FX оставляет желать лучшего. Принтеры серии Epson LQ используют для печати 24 иголки, кроме того, некоторые модели способны печатать цветные изображения (например, Epson LQ-2550). Качество печати принтеров LQ сравнимо с типографским.



Программирование режимов принтера


Для изменения режимов работы принтера и выполнения загрузки шрифтов используются специальные командные последовательности символов. Командные последовательности посылаются в принтер как обычные символы. Вы можете использовать описанные ранее функции MS-DOS или BIOS для вывода этих последовательностей.

Признак начала командной последовательности символов - байт ESC с кодом 1Bh. Вслед за этим байтом программа посылает в принтер саму командную последовательность. Длина последовательности зависит от выполняемой команды.

Первый байт командной последовательности - код выполняемой команды. Далее следует один или несколько байтов параметра команды. Некоторым командам не предшествует байт ESC (это, например, команды перевода строки, страницы или команды табуляции).

Подробное описание всех команд не входит в задачу данной книги. В приложении, однако, приведены полные таблицы команд для принтеров Epson LQ-2550 и Epson FX-1050/850 с краткими пояснениями для каждой команды. Мы опишем подробно лишь несколько команд принтера Epson FX-850/1050 с целью иллюстрации способов программирования с использованием протокола ESC/P.



Работа параллельного принтерного порта


BIOS может работать с тремя параллельными принтерными портами. В процессе тестирования и инициализации системы BIOS находит работоспособные принтерные порты и записывает их базовые адреса в таблицу. Таблица адресов располагается в области данных BIOS по адресу 0000:0408h. Возможны следующие значения базовых адресов:

378h - принтерный порт LPT1;

278h - принтерный порт LPT2;

3BCh - принтерный порт на плате адаптера монохромного дисплея.

Принтерные порты могут вырабатывать запросы на прерывание:

LPT1 - IRQ7, INT0Fh;

LPT2 - IRQ5, INT 0Dh.

Каждый принтерный порт (принтерный адаптер) обслуживают несколько портов ввода/вывода. Рассмотрим их назначение.



Средства BIOS для работы с принтером


BIOS использует для работы с принтером функции 0, 1, 2 прерывания INT17h.

Функция 00h предназначена для печати одного символа:

На входе: AH = 00h;

AL = ASCII-код символа для печати;

DX = номер принтера: 0, 1 или 2.

На выходе: AH = слово состояния принтера (см. ниже).

Эта функция выводит на принтер один символ, заданный в регистре AL. В регистр DX необходимо записать номер используемого принтера, для LPT1 это 0, для LPT2 - 1 и т.д.

После выполнения прерывания регистр AH будет содержать слово состояния, имеющее следующий формат:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ ¦ ¦ L=¦ L= таймаут, слишком большая задержка при ¦ ¦ ¦ ¦ ¦ ¦ выполнении операции печати, возможно, ¦ ¦ ¦ ¦ ¦ ¦ что принтер неисправен; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== не используются; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= ошибка ввода/вывода; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= 1 - принтер выбран для работы; ¦ ¦ ¦ 0 - принтер в состоянии offline; ¦ ¦ ¦ ¦ ¦ L=========== конец бумаги; ¦ ¦ ¦ L============= подтверждение; ¦ L=============== 1 - принтер готов, 0 - принтер занят.

Вызвав функцию 0 прерывания INT 17h, программа должна проверить отдельные биты слова состояния и убедиться в том, что вывод байта произошел без ошибок. Наиболее часто оператор забывает перевести принтер в состояние ONLINE, либо вставить бумагу, либо вообще включить принтер. В этом случае целесообразно напомнить оператору о необходимости выполнения этих действий и затем повторить печать символа.

Если принтер неисправен, программа должна предоставить оператору возможность отменить печать текста. Ниже мы приведем пример программы, выполняющей печать текста и анализирующей ошибки, которые могут возникнуть в процессе печати.

Обратите внимание на бит 1 байта состояния - таймаут. Если принтер находится в состоянии OFFLINE, функция 0 прерывания INT 17h ожидает некоторое время готовности принтера, после чего если принтер так и не перешел в состояние готовности, устанавливает бит 1 в байте состояния. Область данных BIOS по адресу 0000h:0478h содержит четыре байта, которые используются в качестве счетчиков времени при ожидании готовности принтера.


Прерывание INT  17h имеет еще две функции, выпоняющие инициализацию принтера и получающую текущее состояние принтера.

Функция 01h инициализирует принтер:

На входе: AH = 01h;

DX = номер принтера: 0, 1 или 2.

На выходе: AH = слово состояния принтера.

Эта функция выполняет аппаратный сброс принтера. Если вы загрузили в принтер какой-либо шрифт (например, кириллицу), после сброса загрузку шрифта придется выполнять заново. Поэтому не следует выполнять сброс принтера, если это действительно не требуется. Обычно принтер приходится сбрасывать либо перед настройкой его на заданный режим работы, которая выполняется один раз, либо при изменении этого режима.

Слово состояния принтера может быть получено с помощью функции 02h:

На входе: AH = 02h;

DX = номер принтера: 0, 1 или 2.

На выходе: AH = слово состояния принтера.

Эту функцию удобно использовать перед началом печати для определения готовности принтера к работе.

Приведем программу, которая распечатывает содержимое файла с использованием функции 0 прерывания INT 17h.

Программа считывает содержимое файла, открытого в двоичном режиме по байтам. Считанные файлы передаются в качестве параметра функции printchar(), которая и выводит их на принтер. После вызова прерывания INT 17h проверяется состояние принтера, и в случае, когда произошла ошибка ввода/вывода, вызывается обработчик ошибки - функция error(). Эта функция выводит на экран состояние принтера (в развернутом виде, с объяснением каждого бита байта состояния) и запрашивает оператора о дальнейших действиях.

Если оператор может устранить причину ошибки (перевести принтер в состояние ONLINE, вставить бумагу, если она кончилась и т.д.), он нажимает любую клавишу кроме ESC и тогда функция error() возвращает 0. В противном случае возвращается значение 1.

Если оператор решил повторить печать, и, соответственно, если функция error() возвратила значение 0, функция printchar() повторяет печать символа. В противном случае выдается сообщение об ошибке и работа программы завершается.



Итак, приведем исходный текст программы печати содержимого текстовых файлов:

#include <dos.h> #include <stdio.h>

union REGS rg;

int main(int argc, char *argv[]) {

FILE *srcfile;

// Открываем файл, заданный первым параметром // в командной строке. // Если при запуске программы оператор забыл // указать имя файла, выводим напоминающее сообщение.

if( (srcfile = fopen( argv[1], "rb" )) == NULL ) { printf("\nЗадайте имя файла в качестве параметра"); exit(-1); }

// Читаем файл по одному символу, полученный из файла // символ выводим на принтер при помощи функции printchar().

for(;;) { printchar(fgetc(srcfile)); if(feof(srcfile)) break; }

// Закрываем файл.

fclose(srcfile);

}

// ------------------------------------ // Эта функция выводит один символ // на первый принтер (LPT1) // ------------------------------------

int printchar(int chr) {

int status;

// Повторяем в цикле выдачу символа на принтер // до тех пор, пока он не будет выведен без // ошибок, либо пока оператор не отменит // распечатку файла.

for(;;) {

// Дублируем распечатываемый символ на экране

putch(chr);

// Вызываем функцию 0 прерывания INT 17h - // распечатка символа на принтере. // В регистре DX задаем номер принтера LPT1 - это 0.

rg.h.ah = 0; rg.h.al = chr; rg.x.dx = 0;

int86(0x17, &rg, &rg);

// Запоминаем байт состояния принтера // после вывода символа.

status = rg.h.ah;

// Проверяем наличие ошибок. Нас интересуют биты: // // 0 - таймаут (задержка при печати слишком велика) // 3 - ошибка ввода/вывода // 4 - принтер в состоянии ONLINE (1) или OFFLINE (0) // 5 - конец бумаги

if((status & 0x39) != 0x10) {

// Вызываем функцию обработки ошибки error(). Эта // функция возвращает 0, если оператор желает // повторить печать символа, или 1 - если // оператор отменяет печать.

if(error(chr, status)) { printf("\nПечать завершилась аварийно"); exit(-2); } } else break; } }

// ------------------------------------ // Функция выводит на экран состояние // принтера и запрашивает у оператора // требуемые действия - повторить // печать символа или отменить печать. // ------------------------------------

int error(char chr, int status) {

// Выводим состояние принтера после ошибки

printf("\nОшибка принтера %02.2X" "\n\nСостояние принтера:" "\n-------------------", status);

if(status & 1) printf("\nТаймаут при печати");

if(status & 8) printf("\nОшибка ввода/вывода");

if(!(status & 0x10)) printf("\nПринтер находится в состоянии OFFLINE");

if(status & 0x20) printf("\nКонец бумаги");

printf("\n\nДля отмены печати нажмите клавишу ESC," "\nдля повтора - любую другую клавишу\n");

if(getch() == 27) return(1); else return(0);

}


Средства MS-DOS для работы с принтером


Для печати символа на стандартном печатающем устройстве LPT1 (он же PRN) вы можете использовать функцию 05h прерывания MS-DOS INT 21h:

На входе: AH = 05h;

DL = ASCII-код символа для печати.

На выходе: AH = слово состояния принтера (см. ниже).

Команда MS-DOS MODE может переназначить стандартное устройство печати LPT1 на асинхронный последовательный порт:

MODE LPT1:=COM1

Мы подготовили еще одну программу распечатки содержимого файла, но уже при помощи прерывания MS-DOS:

#include <dos.h> #include <stdio.h>

union REGS rg;

int main(int argc, char *argv[]) {

FILE *srcfile;

// Открываем файл, заданный первым параметром // в командной строке. // Если при запуске программы оператор забыл // указать имя файла, выводим напоминающее сообщение.

if( (srcfile = fopen( argv[1], "rb" )) == NULL ) { printf("\nЗадайте имя файла в качестве параметра"); exit(-1); }

// Читаем файл по одному символу, полученный из файла // символ выводим на принтер при помощи функции printchar().

for(;;) { printchar(fgetc(srcfile)); if(feof(srcfile)) break; }

// Закрываем файл.

fclose(srcfile);

}

// ------------------------------------ // Эта функция выводит один символ // на стандартный принтер (LPT1) // ------------------------------------

int printchar(int chr) {

// Дублируем распечатываемый символ на экране

putch(chr);

// Вызываем функцию 5 прерывания INT 21h - // распечатка символа на принтере.

rg.h.ah = 5; rg.h.dl = chr;

int86(0x21, &rg, &rg);

}

Заметьте, что функция 05h прерывания INT 21h не возвращает состояния принтера при ошибке ввода/вывода. Вместо этого вызывается стандартный обработчик критических ошибок MS-DOS, который выводит на экран хорошо знакомое вам сообщение:

Write fault error writing device PRN Abort, Retry, Ignore, Fail?

Вы можете ответить Retry, нажав клавишу "R", тогда MS-DOS выполнит попытку повторить печать символа. Если ответить Abort (нажав клавишу "A"), MS-DOS завершит работу вашей программы.


Поэтому приведенная выше программа не содержит обработчика ошибочных ситуаций error(). Если вас не устраивают действия, выполняемые стандартным обработчиком критических ошибок MS-DOS, вы можете составить собственный. В третьей книге первого тома "Библиотеки системного программиста" мы рассказывали вам о создании и подключении собственного обработчика критических ошибок.

Более интересные возможности по управлению процессом печати предоставляет программа резидентного спулера печати PRINT.COM. Вы знаете, что команда PRINT предназначена для выполнения печати в фоновом режиме.

Оказывается, что если запущена программа PRINT, другие программы могут взаимодействовать с ней, управляя процессом печати.

Для связи со спулером печати можно использовать несколько функций прерывания INT 2Fh:

На входе: AH = 01h;

AL = номер выполняемой операции.

На выходе: AH = 00 - спулер печати не установлен, но его можно установить, запустив программу PRINT; 01 - спулер печати не установлен и его установка невозможна (система не содержит ни одного принтера); FFh - спулер установлен.

Приведем форматы регистров для выполнения различных операций со спулером печати.

На входе: AH = 01h;

AL = 0 - проверить установку спулера печати.

На выходе: AH = 00 - спулер печати не установлен, но его можно установить, запустив программу PRINT; 01 - спулер печати не установлен и его установка невозможна; FFh - спулер установлен.

На входе: AH = 01h;

AL = 1 - передача файла спулеру для печати;

DS:DX = адрес управляющего блока:

Смещение Длина

(+0) 1 уровень запроса, равен 0; (+1) 4 FAR-адрес строки в формате ASCIIZ, содержащей путь файла.

На выходе: AH = 00 - спулер печати не установлен, но его можно установить, запустив программу PRINT; 01 - спулер печати не установлен и его установка невозможна; FFh - спулер установлен.

На входе: AH = 01h;

AL = 2 - отменить печать файла;

DS:DX = адрес строки в формате ASCIIZ, содержащей имя файла, удаляемого из очереди для печати.

На выходе: AH = 00 - спулер печати не установлен, но его можно установить, запустив программу PRINT; 01 - спулер печати не установлен и его установка невозможна; FFh - спулер установлен.



На входе: AH = 01h;

AL = 3 - отменить печать всех файлов.

На выходе: AH = 00 - спулер печати не установлен, но его можно установить, запустив программу PRINT; 01 - спулер печати не установлен и его установка невозможна; FFh - спулер установлен.

На входе: AH = 01h;

AL = 4 - определить состояние спулера и заблокировать спулер.

На выходе: DS:SI = адрес очереди печати (массив строк в формате ASCIIZ, конец массива отмечен строкой, состоящей из 0;

DX = количество ошибок при попытке напечатать последний символ;

AH = 00 - спулер печати не установлен, но его можно установить, запустив программу PRINT; 01 - спулер печати не установлен и его установка невозможна; FFh - спулер установлен.

На входе: AH = 01h;

AL = 5 - разблокировать спулер для продолжения печати.

На выходе: AH = 00 - спулер печати не установлен, но его можно установить, запустив программу PRINT; 01 - спулер печати не установлен и его установка невозможна; FFh - спулер установлен.

Если после вызова перечисленных выше функций флаг переноса CF установлен в 1, регистр AX содержит код ошибки:



1



Неправильный код функции



2



Файл не найден



3



Путь не найден



4



Слишком много открытых файлов



5



Доступ запрещен



6



Неправильный индекс (handle)



8



Переполнение очереди



9



Занято



0Ch



Слишком длинный путь и имя файла (больше 64 байтов)



0Fh



Неправильное определение диска


Установка переключателей конфигурации


Как правило, матричные принтеры позволяют устанавливать режим своей работы с помощью переключателей режима. Для доступа к этим переключателям вам не надо разбирать принтер или снимать крышку корпуса - эти переключатели располагаются в легкодоступном месте.

Обычно используются две группы переключателей - DIP Switch 1 и DIP Switch 2. Приведем назначение первой группы переключателей для принтера Epson FX-850/1050:

Переключатель Назначение

SW 1-1 Используемый набор символов:

ON используются внутренние шрифты принтера; OFF набор символов может быть задан из программы.

SW 1-2 Форма символа "ноль":

ON ноль перечеркнут; OFF ноль не перечеркнут.

SW 1-3 Выбор таблицы символов:

ON используются символы псевдографики; OFF используются символы курсива;

SW 1-4 Тип протокола:

ON эмуляция IBM Proprinter; OFF протокол ESC/P (для принтеров Epson).

SW 1-5 Пропуск перформации на бумаге:

ON не используется; OFF используетя.

SW 1-6,1-7,1-8 Эти переключатели задают национальный набор символов.

Переключатель SW 1-1 необходимо установить в положение OFF в том случае, когда вам необходимо загружать собственный набор символов. Например, в вашем принтере может не быть набора русских символов. В этом случае вам надо использовать специальные программы - русификаторы принтеров. Они будут работать только при правильной установке этого переключателя.

Если вы установите переключатель SW 1-2 в положение ON, все нули в распечатке будут перечеркнуты. Это удобно для распечатки программ, но не всегда приемлемо для документов.

Если вы используете псевдографику, переключатель SW 1-3 должен быть установлен в ON, в противном случае вместо символов псевдографики в распечатке появятся латинские наклонные буквы.

Тип протокола определяется используемым для печати программным обеспечением. Принтер Epson FX-1050/850 может эмулировать систему команд принтера IBM Proprinter. В приложении приведены команды как для протокола Epson6 так и для протокола IBM.

Пропуск перфорации нужен при использовании непрерывной бумажной ленты с перфорацией для отрыва отдельных листов.


Если ваш принтер содержит набор русских символов, переключатели SW 1-6...1-8 должны быть установлены соответствующим образом. Если ваш принтер не печатает русские буквы, проверьте правильность установки этих переключателей.

Назначение второй группы переключателей приведено в таблице:

Переключатель Назначение

SW 2-1 Длина страницы:

ON 12 дюймов; OFF 11 дюймов.

SW 2-2 Использование автоматического податчика бумаги:

ON используется; OFF не используется.

SW 2-3 Использование пропуска для перфорации в 1 дюйм:

ON используется; OFF не используется.

SW 2-4 Автоматический перевод строки при получении символа возврата каретки:

ON используется; OFF не используется.

Для других принтеров назначение и нумерация переключателей может отличаться от описанного выше, но их назначение, как правило, аналогично.

Некоторые принтеры, например, Epson LQ-2550, не имеют переключателей режимов. Для задания режимов используется клавиатура и небольшой дисплей на корпусе принтера. Режим такого принтера хранится в КМОП-памяти, установленной в принтере и питающейся от аккумулятора. Поэтому установленный режим не сбрасывается при выключении питания принтера.


Изменение таблицы векторов прерываний


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

Очень важно не забыть перед завершением работы восстановить содержимое измененных векторов в таблице прерываний, т.к. после завершения работы программы память, которая была ей распределена, считается свободной и может быть использована для загрузки другой программы. Если вы забыли восстановить вектор и пришло прерывание, то система может разрушиться - вектор теперь указывает на область, которая может содержать что угодно.

Поэтому последовательность действий для нерезидентных программ, желающих обрабатывать прерывания, должна быть такой:

прочитать содержимое элемента таблицы векторов прерываний для вектора с нужным вам номером;

запомнить это содержимое (адрес старого обработчика прерывания) в области данных программы;

установить новый адрес в таблице векторов прерываний так, чтобы он соответствовал началу Вашей программы обработки прерывания;

перед завершением работы программы прочитать из области данных адрес старого обработчика прерывания и записать его в таблицу векторов прерываний.

Кроме того, операция изменения вектора прерывания должна быть непрерывной в том смысле, что во время изменения не должно произойти прерывание с номером, для которого производится замена программы обработки. Если вы, например, запишете новое значение смещения, а сегментный адрес обновить не успеете, то по какому адресу будет передано управление в случае прерывания и что при этом произойдет? Об этом можно только догадываться.

Для облегчения работы по замене прерывания DOS предоставляет в ваше распоряжение специальные функции для чтения элемента таблицы векторов прерывания и для записи в нее нового адреса. Если вы будете использовать эти функции, DOS гарантирует, что операция по замене вектора будет выполнена правильно. вам не надо заботиться о непрерывности процесса замены вектора прерывания.


Для чтения вектора используйте функцию 35h прерывания 21h. Перед ее вызовом регистр AL должен содержать номер вектора в таблице. После выполнения функции в регистрах ES:BX будет искомый адрес обработчика прерывания.

Функция 25h прерывания 21h устанавливает для вектора с номером, находящимся в AL, обработчик прерывания DS:DX.

Разумеется, вы можете непосредственно обращаться к таблице векторов прерываний, но тогда при записи необходимо замаскировать прерывания командой CLI, не забыв разрешить их после записи командой STI.

Для пользователей языка Си библиотека QuickC содержит функции _dos_getvec(), _dos_setvect(). Первая функция получает адрес из таблицы векторов прерываний, вторая устанавливает новый адрес.

Если вам надо добавить какие-либо собственные действия к тем, что выполняет стандартный обработчик прерывания, то можно организовать цепочку прерываний.

Для организации цепочки прерываний вы записываете в векторную таблицу адрес своего обработчика, не забыв сохранить прежнее содержимое таблицы. Ваш обработчик получает управление по прерыванию, выполняет какие-либо действия, затем передает управление стандартному обработчику. Можно сделать и по-другому - ваш обработчик вызывает стандартный как подпрограмму, после возврата из стандартного обработчика выполняет дополнительные действия. То есть вы можете вставить дополнительную обработку как до вызова стандартного обработчика, так и после его вызова.

В библиотеке Quick C имеется функция для организации цепочки прерываний - _chain_intr().

Рассмотрим более подробно возможности библиотеки интегрированной среды Quick C, предназначенные для работы с прерываниями.

Модификатор interrupt (_interrupt для Quick C 2.5 и C 6.0) описывает функцию, которая является обработчиком прерывания. Такая функция завершается командой возврата из обработки прерывания IRET, и для нее автоматически генерируются команды сохранения регистров на входе и их восстановления при выходе из обработчика прерывания. Пример использования модификатора для описания функции обработки прерывания:



void interrupt far int_funct(void) { // Тело обработчика прерывания }

Функция обработки прерывания должна быть FAR-функцией, т.к. таблица векторов прерываний содержит полные адреса в виде сегмент:смещение.

Ключевое слово interrupt используется также для описания переменных, предназначенных для хранения векторов прерываний:

void (_interrupt _far *oldvect)(void);

Модификаторы _interrupt и _far для Quick C 2.5 и C 6.0 являются синонимами соответственно interrupt и far.

Какие требования предъявляются к программе обработки прерывания?

Если прерывания происходят часто, то их обработка может сильно замедлить работу прикладной программы. Поэтому обработчик прерывания должен быть короткой, быстро работающей программой, которая выполняет только самые необходимые действия. Например, считать очередной символ из порта принтера и поместить его в буфер, увеличить значение какого-либо глобального счетчика прерываний и т.п.

Для установки своего обработчика прерываний используйте функцию _dos_setvec. Эта функция имеет два параметра - номер прерывания и указатель на новую функцию обработки прерывания. Например:

_dos_setvect(0x16, my_key_intr);

В этом примере для клавиатурного прерывания с номером 16h устанавливается новый обработчик прерывания my_key_intr.

Если вам надо узнать адрес старого обработчика прерывания по его номеру, лучше всего воспользоваться функцией _dos_getvect, которая принимает в качесте параметра номер прерывания и возвращает указатель на соответствующий этому номеру в таблице векторов прерываний обработчик. Например:

old_vector = _dos_getvect(0x16);

Для организации цепочки прерываний используйте функцию _chain_intr. В качестве параметра эта функция принимает адрес старого обработчика прерываний.

Следующий простой пример иллюстрирует применение всех трех функций, предназначенных для работы с прерываниями. Эта программа встраивает собственный обработчик прерывания таймера, который будет вызываться примерно 18,2 раза в секунду. Встраиваемый обработчик прерывания считает тики таймера, и если значение счетчика кратно 20, на динамик компьютера выдается звуковой сигнал. В конце работы новая программа обработки прерывания таймера вызывает старый обработчик с помощью функции _chain_intr.



После установки нового обработчика прерывания таймера основная программа ждет нажатия на клавиатуре любой клавиши, затем она восстанавливает старое содержимое вектора прерывания.

#include <dos.h> #include <stdio.h> #include <stdlib.h>

// Выключаем проверку стека и указателей

#pragma check_stack( off ) #pragma check_pointer( off )

// Это макро используется для выдачи // сигнала на внутренний динамик // компьютера. Используется вывод // в формате TTY символа BELL (7) // через прерывание BIOS 10h

#define BEEP() _asm { \ _asm mov bx,0 \ _asm mov ax, 0E07h \ _asm int 10h \ }

void main(void);

// Объявление программы обработки прерывания

void _interrupt _far timer(void);

// Эта переменная предназначена для хранения // старого значения вектора прерывания // таймера. Она должна быть глобальной.

void (_interrupt _far *oldvect)(void);

// Переменная для подсчета тиков таймера

volatile long ticks;

void main(void) {

ticks=0L; // Сбрасываем счетчик тиков таймера

oldvect = _dos_getvect(0x1c); // Запоминаем адрес // старого обработчика // прерывания

_dos_setvect(0x1c, timer); // Устанавливаем свой // обработчик

printf("\nТаймер установлен. Нажмите любую" "клавишу...\n");

getch(); // Ожидаем нажатия на любую клавишу

_dos_setvect(0x1c,oldvect); // Восстанавливаем старый // обработчик прерывания // таймера exit(0);

}

// Функция обрабатывает прерывания таймера

void _interrupt _far timer(void) {

ticks++; // Увеличиваем счетчик тиков таймера

// Если значение счетчика тиков кратно 20, // выдаем сигнал на динамик компьютера

if((ticks % 20) == 0) BEEP();

// Вызываем старый обработчик прерывания

_chain_intr(oldvect);

}


КОНТРОЛЛЕР ПРЕРЫВАНИЙ


8.1.

8.2.

8.3.

8.4.

8.5.

8.6.

В первой книге первого тома мы уже рассказывали о контроллере прерываний и о механизме прерываний в персональном компьютере IBM PC/XT/AT. Для полноты изложения мы приведем этот материал здесь еще раз, так как контроллер прерываний занимает одно из центральных мест в архитектуре персонального компьютера. Без умения работать с контроллером прерываний вы не сможете использовать режим прямого доступа к памяти, который будет обсуждаться в этой книге.



предназначен для обработки до восьми


Программируемый контроллер прерываний 8259 (отечественный аналог - КР1810ВН59А) предназначен для обработки до восьми приоритетных уровней прерываний. Возможно каскадирование микросхем, при этом общее число уровней прерываний будет достигать 64.

Контроллер 8259 имеет несколько режимов работы, которые устанавливаются программным путем. В персональных компьютерах XT и AT за первоначальную установку режимов работы микросхем 8259 отвечает BIOS. У программиста скорее всего не возникнет потребность перепрограммировать контроллер - это небезопасно, так как неправильное программирование контроллера приведет к нарушению логики работы всей системы.

Однако часто возникает необходимость изменения текущего режима работы (запрет или разрешение прерываний определенного или всех уровней, обработка конца прерывания) или опроса состояния внутренних регистров контроллера. Для этого необходимо ознакомиться со справочными данными на микросхему 8259, где детально описано как первоначальное программирование контроллера, так и управление им во время работы.

Каждому приоритетному уровню прерывания микросхема ставит в соответствие определенный, задаваемый программно, номер прерывания. В разделе книги, посвященном особенностям обработки аппаратных прерываний, приводится такое соответствие для машин типа XT и AT.

Если контроллеры 8259 каскадированы, то ведомой микросхеме присваивается код (выдачей в микросхему соответствующего командного слова). Этот код равен номеру входа IRQ ведущей микросхемы, с которым соединен выход запроса прерывания INT ведомой микросхемы. Внутри микросхемы приоритет зависит от номера IRQ и задается программно. Для компьютеров XT и AT самым высоким приоритетом внутри группы, обслуживаемой каждым контроллером, является вход IRQ0. Однако возможно программное изменение приоритетов в рамках так называемого приоритетного кольца. При этом дно приоритетного кольца имеет самый низкий приоритет.

Приведем возможные варианты задания приоритетов:

Вход Уровни приоритета IRQ0 7 6 5 4 3 2 1 0 IRQ1 0 7 6 5 4 3 2 1 IRQ2 1 0 7 6 5 4 3 2 IRQ3 2 1 0 7 6 5 4 3 IRQ4 3 2 1 0 7 6 5 4 IRQ5 4 3 2 1 0 7 6 5 IRQ6 5 4 3 2 1 0 7 6 IRQ7 6 5 4 3 2 1 0 7



Наиболее высокий приоритет у входа IRQ с обозначением 0 приоритетного кольца, наиболее низкий - с обозначением 7.

Для обработки прерываний контроллер имеет несколько внутренних регистров. Это регистр запросов прерываний IRR, регистр обслуживания прерываний ISR, регистр маски прерываний IMR. В регистре IRR хранятся запросы на обслуживание прерываний от аппаратуры. После выработки сигнала прерывания центральному процессору соответствующий разряд регистра ISR устанавливается в единичное состояние, что блокирует обслуживание всех запросов с равным или более низким приоритетом. Устранить эту блокировку можно либо сбросом соответствующего бита в ISR, либо командой специального маскирования.

Имеется два типа команд, посылаемых программой в контроллер 8259 - команды инициализации и команды операции. Возможны следующие операции:

индивидуальное маскирование запросов прерывания;

специальное маскирование обслуженных запросов;

установка статуса уровней приоритета (по установке исходного состояния, по обслуженному запросу, по указанию);

операции конца прерывания (обычный конец прерывания, специальный конец прерывания, автоматический конец прерывания);

чтение регистров IRR, ISR, IMR.

Мы не будем подробно описывать команды инициализации контроллера 8259, так как программистам они скорее всего не понадобятся. Желающих разобраться во всех тонкостях задания начального режима работы контроллера прерываний мы отсылаем к справочной литературе по микросхеме 8259 или ее отечественному аналогу.

Рассмотрим команды операций. Существуют три типа команд операций:

Маскирование запросов прерывания.

Команды обработки конца прерывания.

Опрос регистров и специальное маскирование.

Байты команды маскирования запросов прерывания выводятся соответственно в порты 21h и A1h для первого и второго контроллера 8259 компьютера AT. Команды операций второго и третьего типа используют порты с адресами 20h и A0h.

Маскирование запросов прерываний мы уже описывали в главе, посвященной прерываниям. Для маскирования какого-либо уровня прерывания надо записать в регистр маски IMR по адресу 21h или A1h единицу в соответствующий разряд регистра.



Команды обработки конца прерывания приведем в виде таблицы:

Биты байта команды Описание D7 D6 D5 D4 D3 D2 D1 D0 0 0 1 0 0 0 0 0 Обычный конец прерывания. 0 1 1 0 0 B2 B1 B0 Специальный конец прерывания, B0...B2 - двоично-десятичный код сбрасываемого разряда в регистре обслуживания прерывания ISR. 1 0 1 0 0 X X X Циклический сдвиг уровней приоритета с обычным концом прерывания. Дно приоритетного кольца устанавливается по обслуженному запросу. 1 1 1 0 0 B2 B1 B0 Циклический сдвиг уровней приоритета со специальным концом прерывания, B0...B2 - двоично-десятичный код дна приоритетного кольца. 1 0 0 0 0 X X X Разрешение вращения уровней приоритета. 0 0 0 0 0 X X X Сброс разрешения вращения уровней приоритета. 1 1 0 0 0 B2 B1 B0 Циклический сдвиг уровней приоритета без завершения прерывания, B0...B2 - двоично-десятичный код дна приоритетного кольца

Команды третьего типа выдаются также в порты с адресами 20h и A0h. Они имеют следующий формат:

Биты байта команды Описание D7 D6 D5 D4 D3 D2 D1 D0 0 0 0 0 1 1 X X Установка режима опроса. 0 0 0 0 1 0 1 1 Разрешение чтения регистра ISR. 0 0 0 0 1 0 1 0 Разрешение чтения регистра IRR. 0 1 1 0 1 0 0 0 Разрешение триггера специального маскирования. 0 1 0 0 1 0 0 0 Сброс триггера специального маскирования.

По команде обычного конца прерывания устанавливается в нулевое состояние разряд ISR, соответствующий последнему обслуженному запросу.

Команда специального конца прерывания устанавливает в нулевое состояние тот разряд ISR, номер которого указан в разрядах B0...B2 команды.

Команда циклического сдвига уровней приоритета с обычным концом прерывания устанавливает в ноль разряд ISR, соответствующий последнему обслуженному запросу и этому же номеру запроса присваивается низший уровень приоритета.

Аналогично работает команда циклического сдвига уровней приоритета со специальным концом прерывания, только низший уровень приоритета присваивается тому входу IRQ, номер которого указан в разрядах B0...B2 команды.

Команда циклического сдвига уровней приоритета устанавливает статус уровней приоритета без выполнения операции конца прерывания. Разряды B0...B2 указывают дно приоритетного кольца.

После выполнения команд разрешения чтения регистров ISR или IRR при выполнении команды ввода из порта 20h и A0h считывается соответственно содержимое регистров ISR и IRR. Для получения содержимого регистра IMR необходимо выполнить чтение портов с адресами соответственно 21h и A1h.

Команда разрешения триггера специального маскирования блокирует действие тех разрядов ISR, которые замаскированы командой типа 1 (маскирования индивидуальных приоритетных уровней запроса прерывания). Специальное маскирование используется для обслуживания такого запроса, который блокируется старшим или равным по уровню приоритета обслуженным запросом, хранящимся в ISR, не сбрасывая последний.

Чтение регистров ISR и IRR может использоваться резидентными программами при проверке возможности своей активизации - можно проверить, не выполняется ли в настоящий момент обработка какого-нибудь прерывания, которая может конфликтовать с действиями резидентной программы.


Маскирование прерываний


Часто при выполнении критических участков программ для того, чтобы гарантировать выполнение определенной последовательности команд целиком приходится запрещать прерывания. Это можно сделать командой CLI. Ее нужно поместить в начало критической последовательности команд, а в конце расположить команду STI, разрешающую процессору воспринимать прерывания. Команда CLI запрещает только маскируемые прерывания, немаскируемые всегда обрабатываются процессором.

Если вы используете запрет прерываний с помощью команды CLI, следите за тем, чтобы прерывания не отключались на длительный период времени, так как это может привести к нежелательным последствиям. Например, будут отставать часы.

Если вам надо запретить не все прерывания, а только некоторые, например, от клавиатуры, то для этого надо воспользоваться услугами контроллера прерываний. Подробно об этом немного ниже, сейчас отметим только, что выдачей в этот контроллер определенной управляющей информации можно замаскировать прерывания от отдельных устройств.



Механизм прерываний.


Для обработки событий, происходящих асинхронно по отношению к выполнению программы, лучше всего подходит механизм прерываний. Прерывание можно рассматривать как некоторое особое событие в системе, требующее моментальной реакции. Например, хорошо спроектированные системы повышенной надежности используют прерывание по аварии в питающей сети для выполнения процедур записи содержимого регистров и оперативной памяти на магнитный носитель, с тем чтобы после восстановления питания можно было продолжить работу с того же места.

Кажется очевидным, что возможны самые разнообразные прерывания по самым различным причинам. Поэтому прерывание рассматривается не просто как таковое, с ним связывают число, называемое номером типа прерывания или просто номером прерывания. С каждым номером прерывания связывается то или иное событие. Система умеет распознавать, какое прерывание, с каким номером произошло и запускает соответствующую этому номеру процедуру.

Программы могут сами вызывать прерывания с заданным номером. Для этого они используют команду INT. Это так называемые программные прерывания. Программные прерывания не являются асинхронными, так как вызываются из программы (а она-то знает, когда она вызывает прерывание!).

Программные прерывания удобно использовать для организации доступа к отдельным, общим для всех программ модулям. Например, программные модули операционной системы доступны прикладным программам именно через прерывания, и нет необходимости при вызове этих модулей знать их текущий адрес в памяти. Прикладные программы могут сами устанавливать свои обработчики прерываний для их последующего использования другими программами. Для этого встраиваемые обработчики прерываний должны быть резидентными в памяти. Мы научимся создавать свои программы обработки прерываний и будем говорить об этом при обсуждении резидентных программ.

Аппаратные прерывания вызываются физическими устройствами и приходят асинхронно. Эти прерывания информируют систему о событиях, связанных с работой устройств, например о том, что наконец-то завершилась печать символа на принтере и неплохо было бы выдать следующий символ, или о том, что требуемый сектор диска уже прочитан его содержимое доступно программе.


Использование прерываний при работе с медленными внешними устройствами позволяют совместить ввод/вывод с обработкой данных в центральном процессоре и в результате повышает общую производительность системы.

Некоторые прерывания (первые пять в порядке номеров) зарезервированы для использования самим центральным процессором на случай каких-либо особых событий вроде попытки деления на ноль, переполнения и т.п.

Иногда желательно сделать систему нечувствительной ко всем или отдельным прерываниям. Для этого используют так называемое маскирование прерываний, о котором мы еще будем подробно говорить. Но некоторые прерывания замаскировать нельзя, это немаскируемые прерывания.

Заметим еще, что обработчики прерываний могут сами вызывать программные прерывания, например, для получения доступа к сервису BIOS или DOS (сервис BIOS также доступен через механизм программных прерываний).

Составление собственных программ обработки прерываний и замена стандартных обработчиков DOS и BIOS является ответственной и сложной работой. Необходимо учитывать все тонкости работы аппаратуры и взаимодействия программного и аппаратного обеспечения. При отладке возможно разрушение операционной системы с непредсказуемыми последствиями, поэтому надо очень внимательно следить за тем, что делает Ваша программа.


Особенности обработки аппаратных прерываний.


Аппаратные прерывания вырабатываются устройствами компьютера, когда возникает необходимость их обслуживания. Например, по прерыванию таймера соответствующий обработчик прерывания увеличивает содержимое ячеек памяти, используемых для хранения времени. В отличие от программных прерываний, вызываемых запланировано самой прикладной программой, аппаратные прерывания всегда происходят асинхронно по отношению к выполняющимся программам. Кроме того, может возникнуть одновременно сразу несколько прерываний!

Для того, чтобы система "не растерялась", решая какое прерывание обслуживать в первую очередь, существует специальная схема приоритетов. Каждому прерыванию назначается свой уникальный приоритет. Если происходит одновременно несколько прерываний, то система отдает предпочтение самому высокоприоритетному, откладывая на время обработку остальных прерываний.

Система приоритетов реализована на двух микросхемах Intel 8259 (для машин класса XT - на одной такой микросхеме). Каждая микросхема обслуживает до восьми приоритетов. Микросхемы можно объединять (каскадировать) для увеличения количества уровней приоритетов в системе.

Уровни приоритетов обозначаются сокращенно IRQ0 - IRQ15 (для машин класса XT существуют только уровни IRQ0 - IRQ7).

Для машин XT приоритеты линейно зависели от номера уровня прерывания. IRQ0 соответствовало самому высокому приоритету, за ним шли IRQ1, IRQ2, IRQ3 и так далее. Уровень IRQ2 в машинах класса XT был зарезервирован для дальнейшего расширения системы. И начиная с машин класса AT IRQ2 стал использоваться для каскадирования контроллеров прерывания 8259. Добавленные приоритетные уровни IRQ8 - IRQ15 в этих машинах располагаются по приоритету между IRQ1 и IRQ3.

Приведем таблицу аппаратных прерываний, расположенных в порядке приоритета:

Номер Описание
8 IRQ0 прерывание интервального таймера, возникает 18,2 раза в секунду.
9 IRQ1 прерывание от клавиатуры. Генерируется при нажатии и при отжатии клавиши. Используется для чтения данных с клавиатуры.
A IRQ2 используется для каскадирования аппаратных прерываний в машинах класса AT.
70 IRQ8 прерывание от часов реального времени.
71 IRQ9 прерывание от контроллера EGA.
72 IRQ10 зарезервировано.
73 IRQ11 зарезервировано.
74 IRQ12 зарезервировано.
75 IRQ13 прерывание от математического сопроцессора.
76 IRQ14 прерывание от контроллера жесткого диска.
77 IRQ15 зарезервировано.
B IRQ3 прерывание асинхронного порта COM2.
C IRQ4 прерывание асинхронного порта COM1.
D IRQ5 прерывание от контроллера жесткого диска для XT.
E IRQ6 прерывание генерируется контроллером флоппи диска после завершения операции
F IRQ7 прерывание принтера. Генерируется принтером, когда он готов к выполнению очередной операции. Многие адаптеры принтера не используют это прерывание.
<
#include <stdio.h> #include <stdlib.h> #include <conio.h>

void main(void);

void main(void) {

outp(0x21,0); printf("\nПрерывания от флоппи-диска разрешены.\n");

exit(0); }

Заметьте, что мы только что замаскировали прерывание именно от флоппи-диска, все остальные устройства продолжали нормально работать. Если бы мы выдали машинную команду CLI, то отключились бы все аппаратные прерывания. Это привело бы, например, к тому, что клавиатура была бы заблокирована.

Еще одно замечание, касающееся обработки аппаратных прерываний. Если вы полностью заменяете стандартный обработчик аппаратного прерывания, не забудьте в конце программы выдать байт 20h в порт с адресом 20h (A0h для второго контроллера 8259). Эти действия необходимы для очистки регистра обслуживания прерывания ISR. При этом разрешается обработка прерываний с более низким приоритетом, чем то, которое только что обрабатывалось.

Если вы обрабатываете прерывание 1Ch, то добавка в конце программы не нужна, так как это прерывание является расширением другого прерывания (прерывания таймера).

Перед тем, как завершить изучение прерываний, зададимся вопросом - можно ли замаскировать немаскируемое прерывание? Оказывается, можно!

Конечно, если сигнал прерывания пришел на вход немаскируемого прерывания процессора, ничего сделать нельзя - прерывание произойдет неизбежно. Но в компьютерах XT и AT предусмотрены схемы, блокирующие вход немаскируемого прерывания процессора NMI.

Для XT маскированием немаскируемого прерывания управляет порт с адресом 0A0h. Если записать в него 0, немаскируемое прерывание будет запрещено, если 80h - разрешено.

Аналогично для AT маскированием немаскируемого прерывания управляет бит 7 порта 70h. Запись байта 0ADh в порт 70h запретит немаскируемое прерывание, а байта 2Dh - разрешит прохождение прерывания.

Заметим, что мы не запрещаем немаскируемое прерывание "внутри" процессора - это невозможно по определению, мы "не пускаем" сигнал прерывания на вход NMI.


Таблица векторов прерываний


Для того чтобы связать адрес обработчика прерывания с номером прерывания, используется таблица векторов прерываний, занимающая первый килобайт оперативной памяти - адреса от 0000:0000 до 0000:03FF. Таблица состоит из 256 элементов - FAR-адресов обработчиков прерываний. Эти элементы называются векторами прерываний. В первом слове элемента таблицы записано смещение, а во втором - адрес сегмента обработчика прерывания.

Прерыванию с номером 0 соответствует адрес 0000:0000, прерыванию с номером 1 - 0000:0004 и т.д. Для программиста, использующего язык Си, таблицу можно описать следующим образом:

void (* interrupt_table[256])();

Инициализация таблицы происходит частично BIOS после тестирования аппаратуры и перед началом загрузки операционной системой, частично при загрузке DOS. DOS может переключить на себя некоторые прерывания BIOS.

Займемся теперь содержимым таблицы векторов прерываний. Приведем назначение некоторых наиболее важных векторов:

Номер Описание
0 Ошибка деления. Вызывается автоматически после выполнения команд DIV или IDIV, если в результате деления происходит переполнение (например, при делении на 0). DOS обычно при обработке этого прерывания выводит сообщение об ошибке и останавливает выполнение программы. Для процессора 8086 при этом адрес возврата указывает на следующую после команды деления команду, а в процессоре 80286 - на первый байт команды, вызвавшей прерывание.
1 Прерывание пошагового режима. Вырабатывается после выполнения каждой машинной команды, если в слове флагов установлен бит пошаговой трассировки TF. Используется для отладки программ. Это прерывание не вырабатывается после выполнения команды MOV в сегментные регистры или после загрузки сегментных регистров командой POP.
2 Аппаратное немаскируемое прерывание. Это прерывание может использоваться по-разному в разных машинах. Обычно вырабатывается при ошибке четности в оперативной памяти и при запросе прерывания от сопроцессора.
3 Прерывание для трассировки. Это прерывание генерируется при выполнении однобайтовой машинной команды с кодом CCh и обычно используется отладчиками для установки точки прерывания.
4 Переполнение. Генерируется машинной командой INTO, если установлен флаг OF. Если флаг не установлен, то команда INTOвыполняется как NOP. Это прерывание используется для обработки ошибок при выполнении арифметических операций.
5 Печать копии экрана. Генерируется при нажатии на клавиатуре клавиши PrtScr. Обычно используется для печати образа экрана. Для процессора 80286 генерируется при выполнении машинной команды BOUND, если проверяемое значение вышло за пределы заданного диапазона.
6 Неопределенный код операции или длина команды больше 10 байт (для процессора 80286).
7 Особый случай отсутствия математического сопроцессора (процессор 80286).
8 IRQ0 - прерывание интервального таймера, возникает 18,2 раза в секунду.
9 IRQ1 - прерывание от клавиатуры. Генерируется при нажатии и при отжатии клавиши. Используется для чтения данных от клавиатуры.
A IRQ2 - используется для каскадирования аппаратных прерываний в машинах класса AT.
B IRQ3 - прерывание асинхронного порта COM2.
C IRQ4 - прерывание асинхронного порта COM1.
D IRQ5 - прерывание от контроллера жесткого диска для XT.
E IRQ6 - прерывание генерируется контроллером флоппи-диска после завершения операции.
F IRQ7 - прерывание принтера. Генерируется принтером, когда он готов к выполнению очередной операции. Многие адаптеры принтера не используют это прерывание.
10 Обслуживание видеоадаптера.
11 Определение конфигурации устройств в системе.
12 Определение размера оперативной памяти в системе.
13 Обслуживание дисковой системы.
14 Последовательный ввод/вывод.
15 Расширенный сервис для AT-компьютеров.
16 Обслуживание клавиатуры.
17 Обслуживание принтера.
18 Запуск BASIC в ПЗУ, если он есть.
19 Загрузка операционной системы.
1A Обслуживание часов.
1B Обработчик прерывания Ctrl-Break.
1C Прерывание возникает 18.2 раза в секунду, вызывается программно обработчиком прерывания таймера.
1D Адрес видеотаблицы для контроллера видеоадаптера 6845.
1E Указатель на таблицу параметров дискеты.
1F Указатель на графическую таблицу для символов с кодами ASCII 128-255.
20-5F Используется DOS или зарезервировано для DOS.
60-67 Прерывания, зарезервированные для пользователя.
68-6F Не используются.
70 IRQ8 - прерывание от часов реального времени.
71 IRQ9 - прерывание от контроллера EGA.
72 IRQ10 - зарезервировано.
73 IRQ11 - зарезервировано.
74 IRQ12 - зарезервировано.
75 IRQ13 - прерывание от математического сопроцессора.
76 IRQ14 - прерывание от контроллера жесткого диска.
77 IRQ15 - зарезервировано.
78 - 7F Не используются.
80-85 Зарезервированы для BASIC.
86-F0 Используются интерпретатором BASIC.
F1-FF Не используются.

IRQ0-IRQ15 - это аппаратные прерывания, о них будет рассказано позже.



Инициализация канала DMA


Для инициализации канала программа должна выполнить следующие шаги:

сбросить триггер байтов командой записи в регистр0Ch;

задать режим работы канала, выполнив запись по адресу 0Bh в регистр режима MR;

заслать младшие 16 битов 20-битового адреса области памяти, которая будет использована для передачи данных, в регистр базового адеса (адрес порта зависит от номера канала: 0 канал использует адрес 00h, 1 канал - 02h, 2 канал - 04h, 3 канал - 06h);

заслать номер страницы (старшие 4 бита 20-битового адреса) в регистр страниц 81h;

загрузить регистр циклов прямого доступа к памяти CWR значением, на 1 меньше требуемого количества передаваемых байтов (адреса этих портов для каналов 0...3, соответственно, 01h, 03h, 05h, 07h;

разрешить работу канала, выполнив запись в регистр маски каналов по адресу 0Ah.

Сразу после разрешения канал начинает передачу данных. После окончания передачи данных устройство обычно вырабатывает прерывание, которое служит признаком окончания передачи данных.



Контроллер прямого доступа для IBM AT


Контроллер DMA компьютера IBM AT совместим снизу вверх с контролером IBM PC/XT. Он состоит из двух каскадно включенных микросхем Intel 8237A-5. Второй контроллер обслуживает каналы DMA с номерами 4-7.

Приведем назначение каналов DMA для IBM AT:

0 зарезервировано;
1 управление синхронной передачей данных SDLC (Synchronous Data Link Control);
2 адаптер накопителя на гибком магнитном диске (НГМД);
3 адаптер накопителя на магнитном диске (НМД);
4 используется для каскадного соединения с первым контроллером DMA;
5-6 зарезервировано.

Другое отличие - это разрядность каналов. Каналы 0-3 являются каналами 8-битовой передачи данных, а каналы 4-7 обеспечивают 16-битовую передачу данных. В связи с этим используются все 8 битов регистров страниц. Формируется 24-битовый адрес из 16-ти младших битов адреса, записываемых в базовые регистры и 8-ми старших битов адреса, записываемых в регистры страниц.

Размер страницы составляет 128 килобайт, поэтому при передаче данных с использованием DMA не должна пересекаться граница 128 килобайт.

Приведем назначение и адреса регистров страниц контроллера для IBM AT:

81h Регистр страниц канала 2
82h Регистр страниц канала 3
83h Регистр страниц канала 1
87h Регистр страниц канала 0
89h Регистр страниц канала 6
8Bh Регистр страниц канала 5
8Ah Регистр страниц канала 7
8Fh Регенерация динамической памяти

Для 16-битовых каналов 4-7 передача данных начинается с границы слова и все адреса относятся к 16-битовым словам.



Контроллер прямого доступа для IBMPC/XT


Контроллер прямого доступа для IBM PC/XT реализован на базе микросхемы Intel 8237A и содержит четыре канала. Эти каналы используются следующим образом:

0 обновление содержимого динамической памяти компьютера, этот канал имеет наивысший приоритет;
1 не используется;
2 адаптер накопителя на гибком магнитном диске (НГМД);
3 адаптер накопителя на магнитном диске (НМД) - этот канал имеет низший приоритет.



КОНТРОЛЛЕР ПРЯМОГО ДОСТУПА К ПАМЯТИ


9.1.

9.2.

Прямой доступ к памяти (Direct Memory Access - DMA) используется для выполнения операций передачи данных непосредственно между оперативной памятью и устройствами ввода/вывода. Обычно это такие устройства, как НГМД, НМД, кассетные накопители на магнитной ленте КНМЛ (стримеры).

При использовании DMA процессор не участвует в операциях ввода/вывода, контроллер прямого доступа сам формирует все сигналы, необходимые для обмена данными с устройством. Скорость такого непосредственного обмена значительно выше, чем при традиционном вводе/выводе с использованием центрального процессора и команд INP, OUT.

Мы уже немного рассказывали о контроллере прямого доступа к памяти в третьей книге первого тома, в разделе, посвященном работе с НГМД на уровне команд ввода/вывода. Была приведена программа, использующая DMA для чтения секторов дискеты. В этом разделе мы подробнее рассмотрим порты контроллера DMA.

Распространены два типа контроллеров DMA - контроллеры для IBM PC/XT и контроллеры для IBM AT. Вначале мы расскажем о первом типе контроллеров, затем займемся контроллером DMA компьютера IBM AT.



Порт 0Ah


Регистр маски. Используется для маскирования запросов на прямой доступ для отдельных каналов:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ LT+-+-+-+T+T+T+T- L===T===- ¦ L=¦= номер канала: ¦ ¦ 00 - канал 0; ¦ ¦ 01 - канал 1; ¦ ¦ 10 - канал 2; ¦ ¦ 11 - канал 3; ¦ ¦ ¦ L===== 0 - установить маску; ¦ 1 - сбросить маску; ¦ L=========== не используются.



Порт 0Bh


Регистр режима. Служит для определения режимов работы каналов контроллера DMA:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- L=¦ ¦ ¦ L=¦ L=¦= номер канала: ¦ ¦ ¦ ¦ 00 - канал 0; ¦ ¦ ¦ ¦ 01 - канал 1; ¦ ¦ ¦ ¦ 10 - канал 2; ¦ ¦ ¦ ¦ 11 - канал 3; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== тип цикла DMA: ¦ ¦ ¦ 00 - цикл проверки; ¦ ¦ ¦ 01 - цикл записи; ¦ ¦ ¦ 10 - цикл чтения; ¦ ¦ ¦ 11 - запрещенная комбинация; ¦ ¦ ¦ ¦ ¦ L========= 1 - режим автоинициализации; ¦ ¦ ¦ L=========== приращение адреса: ¦ 0 - инкрементирование; ¦ 1 - декрементирование; ¦ L============= режим обслуживания: 00 - передача по требованию; 01 - одиночная передача; 10 - блочная передача; 11 - каскадироание.



Порт 0Ch


Сброс триггера байтов. Для загрузки внутренних 16-разрядных регистров контроллера используется последовательный вывод младшего, затем старшего байтов слова. После сброса триггера байтов можно начинать загрузку 16-разрядных регистров.



Порт 0Dh


Запись в этот порт вызывает сброс контроллера. Для дальнейшего использования контроллер должен быть заново проинициализирован.



Порт 0Eh


Сброс регистра маски. После записи в этот регистр любого значения разрешается работа всех четырех каналов прямого доступа.



Порт 0Fh


Маскирование/размаскирование каналов. С помощью этого порта можно выполнить одновременное маскирование или размаскирование нескольких каналов:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ LT+-+-+T+T+T+T+T- L==T==- ¦ ¦ ¦ L= 1 - маскирование канала 0; ¦ ¦ ¦ ¦ 0 - разрешение канала 0; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== 1 - маскирование канала 1; ¦ ¦ ¦ 0 - разрешение канала 1; ¦ ¦ ¦ ¦ ¦ L=====1 - маскирование канала 2; ¦ ¦ 0 - разрешение канала 2; ¦ ¦ ¦ L======= 1 - маскирование канала 3; ¦ 0 - разрешение канала 3; ¦ L============ не используются.



Порт 08h.


Этот порт используется при записи в качестве управляющего регистра и при чтении как регистр состояния.

Формат управляющего регистра:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ ¦ ¦ ¦ ¦ L= 1 - использование режима память-память; ¦ ¦ ¦ ¦ ¦ ¦ ¦ 0 - обычный режим работы; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== если используется режим память-память, ¦ ¦ ¦ ¦ ¦ ¦ то 1 в этом разряде разрешает захват ¦ ¦ ¦ ¦ ¦ ¦ канала, 0 - запрещает; ¦ ¦ ¦ ¦ ¦ ¦ в обычном режиме работы состояние этого ¦ ¦ ¦ ¦ ¦ ¦ бита безразлично; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=====1 - запрет работы DMA; ¦ ¦ ¦ ¦ ¦ 0 - разрешение работы DMA; ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= 1 - использование сжатия во времени, если ¦ ¦ ¦ ¦ установлен бит обычного режима работы; ¦ ¦ ¦ ¦ 0 - обычный режим работы; ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= 1 - вращение приоритетов; ¦ ¦ ¦ 0 - фиксированные приоритеты; ¦ ¦ ¦ ¦ ¦ L=========== 1 - удлиненный цикл записи; ¦ ¦ 0 - нормальный цикл записи; ¦ ¦ ¦ L============= 1 - используется низкий уровень для ¦ сигнала запроса на DMA DREQ; ¦ 0 - используется высокий уровень; ¦ L=============== 1 - используется высокий уровень для сигнала подтверждения DMA DACK; 0 - используется низкий уровень;

Обычно этот регистр инициализируется BIOS в процессе тестирования системы и впоследствии изменять режим работы контроллера DMA не требуется. Ошибки при инициализации этого порта могут привести к "зависанию" системы.

При чтении из порта 08h программа получает слово состояния контроллера DMA:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ LT+-+-+T+T+-+-+T- L==T==- L=====¦= биты 0-3 устанавливаются в 1 при ¦ достижении счетчиками каналов 0-3 ¦ конечных значений; ¦ L============ биты 4-7 установлены в 1, если имеется разрешение на DMA соответственно, каналов 0-3.



Порт 09h.


Регистр запроса. Предназначен для организации программного (а не аппаратного) запроса на DMA. Для использования программного запроса канал должен быть запрограммирован в режиме блочной передачи.

Формат регистра:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ LT+-+-+-+T+T+T+T- L===T===- ¦ L=¦= номер используемого канала: ¦ ¦ 00 - канал 0; ¦ ¦ 01 - канал 1; ¦ ¦ 10 - канал 2; ¦ ¦ 11 - канал 3; ¦ ¦ ¦ L===== 0 - установить запрос; ¦ 1 - сбросить запрос; ¦ L=========== не используются.



Порты 0C0h - 0DFh


Эти регистры содержат базовые адреса и счетчики передаваемых данных каналов 4-7. Их назначение приводится в следующей таблице:

0C0h Запись: Базовый адрес канала 4
Чтение: Текущий адрес
0C2h Запись: Счетчик канала 4
Чтение: Текущий адрес
0C4h Запись: Базовый адрес канала 5
Чтение: Текущий адрес
0C6h Запись: Счетчик канала 5
Чтение: Текущий адрес
0C8h Запись: Базовый адрес канала 6
Чтение: Текущий адрес
0CAh Запись: Счетчик канала 6
Чтение: Текущий адрес
0CCh Запись: Базовый адрес канала 7
Чтение: Текущий адрес
0CEh Запись: Счетчик канала 7
Чтение: Текущий адрес



Порты 0D0h-0DFh


Это управляющие порты и порты состояния второй микросхемы 8237A-5. По формату и назначению они соответствуют рассмотренным ранее для контроллера DMA компьютеров IBM PC/XT:

0D0h Управляющий регистр / регистр состояния
0D2h Регистр запроса
0D4h Регистр маски
0D6h Регистр режима
0D8h Сброс триггера байтов
0DAh Сброс контроллера
0DCh Сброс регистра маски
0DEh Маскирование/размаскирование каналов

В качестве примера использования контроллера прямого доступа к памяти приведем программу чтения сектора флоппи-диска. Мы уже описывали ее в предыдущем томе. Поэтому здесь мы не будем описывать команды контроллера НГМД и другие тонкости, имеющие отношение к работе с флоппи-дисками.

Перед началом инициализации КПДП программа должна послать в порты 0Bh и 0Ch код операции, которая будет выполняться КПДП - 46h для операции чтения и 4Ah для операции записи.

В процессе инициализации программа должна сообщить КПДП адрес буфера, куда ему следует поместить данные или откуда надо взять данные, и длину передаваемых данных в байтах.

Адрес необходимо представить в виде номера страницы и смещения. Для КПДП машины AT используется восьмибитовый номер страницы и 16-битовое смещение. Например, для адреса 23456 номер страницы - 2, смещение - 3456.

Для программирования канала 2 КПДП программа должна сначала вывести младший байт смещения в порт с адресом 4, затем вывести в этот же порт старший байт смещения и, наконец, вывести байт номера страницы в порт с адресом 81h.

Длина передаваемых данных выводится аналогично в порт с адресом 5 - сначала младший байт длины, затем старший.

После определения режима работы канала, адреса буфера и длины передаваемых данных, программа должна разрешить работу КПДП, выдав в порт с адресом 0Ch байт 2. Теперь канал прямого доступа готов к работе и будет ждать данных от контроллера НГМД.

Приведенная ниже демонстрационная программа использует несколько наиболее характерных команд контроллера НГМД. Она предназначена для работы на машине AT. Для того, чтобы она правильно работала и на машинах PC/XT, ее надо немного изменить. Изменения касаются программирования контроллера ПДП и программирования скорости передачи контроллера НГМД.


Контроллер КПДП PC/XT использует 4- битовый номер страницы буфера вместо 8-битового. Скорость передачи контроллера НГМД в машинах PC/XT не программируется, вам надо убрать соответствующие строки из программы. Еще надо обратить внимание на различное быстродействие машин AT и PC/XT и скорректировать константы в строках программы, выполняющих задержку.

Программа не проверяет, установлен ли флоппи-диск в приемный карман дисковода, поэтому перед запуском не забудьте установить диск.

#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <dos.h> #include "sysp.h"

#define CYL 0

void main(void); void fdc_out(unsigned char byte); int fdc_inp(void); void int_wait(void); void dma_init(char *);

void main(void) {

unsigned i; long l; char buffer[512]; char status[7], main_status; DPT _far *fdpt; FILE *sect;

printf("\n" "\nРабота с контроллером НГМД" "\n ©Фролов А., 1991" "\n");

// Эта программа предназначена только для IBM AT

if(pc_model() != 0xfc) { printf("Эта программа предназначена только для IBM AT\n"); exit(-1); }

// Открываем файл, в который будем записывать // содержимое самого первого сектора на дискете

sect = fopen("!sector.dat","wb+");

// Устанавливаем указатель на таблицу // параметров дискеты

fdpt = get_dpt();

// Включаем мотор дисковода А: // Перед этим разрешаем прерывания

_enable(); outp(0x3F2, 0x1C);

// Выполняем задержку для разгона двигателя

for(l=0;l<200000;l++);

// Показываем содержимое регистра основного // состояния контроллера

printf("Мотор включен.\t\t"); printf("Основное состояние: %02.2X\n",inp(0x3F4));

// Перед чтением сектора необходимо установить // головку на нужную дорожку, в нашем случае это // дорожка с номером CYL.

// Выдаем контроллеру команду "Поиск"

fdc_out(0xf);

// Для команды "Поиск" требуется два байта параметров: // номер головки/номер накопителя и номер дорожки. // Мы работаем с нулевой головкой накопителя А:, // поэтому первый параметр равен 0, второй - CYL



fdc_out(0); fdc_out(CYL);

// Показываем содержимое регистра основного // состояния контроллера

printf("\n<<<Поиск>>> \t\t"); printf("Основное состояние: %02.2X\n",inp(0x3F4));

// Ожидаем прерывание по завершению операции

int_wait();

// Задержка для позиционирования головки

for(l=0;l<20000;l++);

// Для проверки результата выполнения команды // "Поиск" выдаем контроллеру команду // "Чтение состояния прерывания"

// Выводим содержимое регистра состояния // ST0 и номер дорожки после выполнения команды // "Поиск" PCN

fdc_out(0x8); printf("Состояние прерывания:\t"); printf(" ST0: %02.2X, \t", fdc_inp()); printf("PCN: %02.2X\n", fdc_inp());

// Для более глубокой диагностики состояния // контроллера выдаем контроллеру команду // "Чтение состояния накопителя", выводим // содержимое регистра состояния ST3

fdc_out(4); fdc_out(0); printf("Состояние накопителя:\t ST3: %02.2X\n",fdc_inp());

// Устанавливаем скорость передачи данных 500 Кбайтов/с, // это значение может различаться для разных типов дискет

outp(0x3F7, 0);

// Инициализация канала прямого // доступа к памяти

dma_init(buffer);

// Выдаем команду "Чтение данных"

fdc_out(0x66); fdc_out(0x0); // накопитель 0, головка 0

fdc_out(CYL); // цилиндр CYL fdc_out(0); // головка 0 fdc_out(1); // номер сектора - 1

// Передаем контроллеру технические параметры // дисковода, берем их из таблицы параметров дискеты. // Это такие параметры: // - размер сектора; // - номер последнего сектора на дорожке; // - размер промежутка; // - число считываемых/записываемых байтов

fdc_out(fdpt->sec_size); fdc_out(fdpt->eot); fdc_out(fdpt->gap_rw); fdc_out(fdpt->dtl);

// Ожидаем прерывание по завершению операции

int_wait();

// Считываем и выводим на экран байты результата // операции "Чтение данных"

printf("\n<<<Чтение сектора>>> \n"); printf(" Байты состояния (ST0,ST1,ST2,C,H,R,N):\n");



for(i=0; i<7; i++) printf("%02.2X\t", (char) fdc_inp()); printf("\n");

// Выводим содержимое считанного сектора в файл

for(i=0; i<512; i++) fputc(buffer[i],sect); fclose(sect);

// Выключаем мотор

outp(0x3F2, 0xC); }

// Вывод байта в контроллер дисковода

void fdc_out(unsigned char parm) {

_asm { mov dx,3F4h // Порт основного состояния loop_fdc_out:

in al,dx test al,80h // Проверяем готовность jz loop_fdc_out // контроллера

inc dx // Выводим байт в порт данных mov al, parm // контроллера out dx, al } }

// Ввод байта из порта данных контроллера дисковода

int fdc_inp(void) {

_asm { mov dx,3F4h // Порт основного состояния loop_fdc_inp: in al,dx test al,80h // Проверяем готовность jz loop_fdc_inp // контроллера

inc dx // Введенный байт записываем in al, dx // в регистр AX } }

// Ожидание прерывания от контроллера

void int_wait(void) {

// Разрешаем прерывания

_enable(); _asm { mov ax,40h // После прихода прерывания mov es,ax // программа обработки прерывания mov bx,3Eh // устанавливает в 1 старший бит wait_loop: // байта в области данных BIOS mov dl,es:[bx] // по адресу 0040:003E. test dl,80h // Мы ждем, когда этот бит будет jz wait_loop // установлен в 1, а затем // сбрасываем его. and dl,01111111b mov es:[bx],dl } }

// Инициализация канала прямого доступа к памяти

void dma_init(char *buf) {

unsigned long f_adr; unsigned sg, of;

// Вычисляем 24-разрядный адрес буфера для данных

f_adr = ((unsigned long)_psp << 4) + (((unsigned long)buf) & 0xffff);

// Расщепляем адрес на номер страницы // и смещение

sg = (f_adr >> 16) & 0xff; of = f_adr & 0xffff;

// На время программирования контроллера прямого // доступа запрещаем прерывания

_disable();

_asm { mov al,46h // Команда чтения данных от // контроллера НГМД.

out 12,al // Сброс триггера-указателя байта // для работы с 16-разрядными портами. // Следующий байт, выводимый в 16-разрядный // порт будет интерпретироваться // как младший.

out 11,al // Установка режима контроллера ПДП



mov ax,of // Смещение буфера, младший байт out 4,al mov al,ah // Смещение буфера, старший байт out 4,al

mov ax,sg // Номер страницы out 81h,al

mov ax,511 // Длина передаваемых данных out 5,al mov al,ah out 5,al

mov al,2 // Разблокировка канала 2 контроллера ПДП out 10,al }

// Инициализация контроллера закончена, // разрешаем прерывания.

_enable(); }

Остальные команды вы можете попробовать сами. Для получения дополнительной информации по контроллеру НГМД обратитесь к техническому руководству по IBM PC. Многое можно почерпнуть из описания микросхем дискового контроллера 765 фирмы NEC и аналогов этой микросхемы - Intel 8272A и отечественной КР1810ВГ72А.

На этом мы завершим обсуждение контроллера DMA. Советуем вам еще раз посмотреть программу, читающую сектора диска с использованием канала прямого доступа в памяти, которую мы приводили в третьей книге первого тома. Вы можете самостоятельно внести в нее некоторые усовершенствования, например, проверку перехода адреса в процессе работы канала прямого доступа через границу 128 килобайтов.


Порты 00h - 07h


Эти регистры содержат базовые адреса и счетчики передаваемых данных каналов 0 - 3. Их назначение приводится в следующей таблице:

00h Запись: Базовый адрес канала 0
Чтение: Текущий адрес
01h Запись: Счетчик канала 0
Чтение: Текущий адрес
02h Запись: Базовый адрес канала 1
Чтение: Текущий адрес
03h Запись: Счетчик канала 1
Чтение: Текущий адрес
04h Запись: Базовый адрес канала 2
Чтение: Текущий адрес
05h Запись: Счетчик канала 2
Чтение: Текущий адрес
06h Запись: Базовый адрес канала 3
Чтение: Текущий адрес
07h Запись: Счетчик канала 3
Чтение: Текущий адрес



Порты 81h-8Fh


Это порты регистров страниц.

Для работы с памятью контроллер прямого доступа использует 20-разрядные физические адреса. Шестнадцать младших битов адреса необходимо записать в регистр базового адреса канала. Старшие четыре бита - биты 16-19 - должны быть записаны в соответствующие порты регистров страниц.

При инициализации регистров базового адреса и регистра страниц необходимо следить за тем, чтобы в процессе передачи данных не происходил переход за границу 64 килобайта.

Для адресации регистров страниц можно использовать следующие порты:

81h Регистр страниц канала 2
82h Регистр страниц канала 3
83h Регистр страниц канала 1



Регистры каналов DMA


Каждый канал содержит 16-разрядные регистры:

регистр текущего адреса CAR, содержит текущий адрес ячейки памяти при выполнении операции обмена данными с использованием DMA;

регистр циклов прямого доступа к памяти CWR, содержит число слов, предназначенных для передачи минус единица; при выполнении обмена данными регистр работает в режиме вычитания;

регистр хранения базового адреса BAR, используется для хранения базового адреса памяти, используемого при передачи данных; в процессе работы канала DMA содержимое этого регистра не изменяется;

регистр хранения базового числа циклов прямого доступа к памяти WCR; он хранит число циклов DMA, его содержимое также не изменяестя;

регистр режима MR, определяющий режим работы канала.

Приведем адреса регистров и их форматы для компьютеров IBMPC/XT.