5. Асинхронный адаптер

Практически каждый компьютер оборудован хотя бы одним асинхронным последовательным адаптером. Обычно он представляет собой отдельную плату или же расположен прямо на материнской плате компьютера. Его иногда называют асинхронным адаптером RS-232-C, или портом RS-232-C. Асинхронный адаптер обычно содержит несколько COM-портов, через которые к компьютеру можно подключать внешние устройства.

Каждому COM-порту соответствует несколько регистров, через которые программа получает к нему доступ, и определенная линия IRQ для сигнализирования компьютеру об изменении состояния порта. На этапе инициализации модули BIOS присваивают каждому COM-порту уникальный номер. Например, компьютер может иметь четыре порта COM1 - COM4.

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

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

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

Модем также подключается к компьютеру через COM-порт и позволяет обмениваться данными по обычным телефонным линиям.

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

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

Рис. 5.1. Формат данных, передаваемых через COM-порт

Из рисунка видно, что исходное состояние линии последовательной передачи данных - уровень логической 1. Это состояние линии называют отмеченным - MARK. Когда начинается передача данных, уровень линии переходит в 0. Это состояние линии называют пустым - SPACE. Если линия находится в таком состоянии больше определенного времени, считается, что линия перешла в состояние разрыва связи - BREAK.

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

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

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

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

Другая важная характеристика - скорость передачи данных. Скорость передачи данных обычно измеряется в битах за секунду (bps) или в бодах (по фамилии французского изобретателя телеграфного аппарата Emile Baudot - Э. Бодо). Боды определяют количество передаваемых битов за секунду. При этом учитываются и старт/стопные биты, а также бит четности.

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

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

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

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

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

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

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

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

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

Внешне каждый COM-порт асинхронного последовательного адаптера представлен собственным разъемом. Существует два стандарта на разъемы COM-порта: это DB25 и DB9. Первый разъем имеет 25, а второй 9 выводов. Несмотря на то, что разъем DB25 содержит в два с половиной раза больше выводов, чем DB9, они передают одинаковые сигналы. При необходимости можно приобрести переходник между разъемами DB25 и DB9.

Внутренний модем содержит COM-порт внутри себя, поэтому на плате внутреннего модема вы не обнаружите ни одного разъема COM-порта.

Приведем разводку разъема DB25 со стороны последовательного асинхронного адаптера:

Номер контакта Назначение контакта Вход или выход
1 Защитное заземление (Frame Ground, FG) -
2 Передаваемые данные (Transmitted Data, TD) Выход
3 Принимаемые данные (Received Data, RD) Вход
4 Запрос для передачи (Request to send, RTS) Выход
5 Сброс для передачи (Clear to Send, CTS) Вход
6 Готовность данных (Data Set Ready, DSR) Вход
7 Сигнальное заземление (Signal Ground, SG) -
8 Детектор принимаемого с линии сигнала (Data Carrier Detect, DCD). Иногда сигнал DCD обозначают как CD (Carrier Detect) или RLSD (Receive Line Signal Detect) Вход
9-19 Не используются  
20 Готовность выходных данных (Data Terminal Ready, DTR) Выход
21 Не используется  
22 Индикатор вызова (Ring Indicator, RI) Вход
23-25 Не используются  

Теперь приведем разводку разъема DB9 со стороны последовательного асинхронного адаптера:

Номер контакта Назначение контакта Вход или выход
1 Детектор принимаемого с линии сигнала (Data Carrier Detect, DCD). Иногда сигнал DCD обозначают как CD или RLSD Вход
2 Принимаемые данные (Received Data, RD) Вход
3 Передаваемые данные (Transmitted Data, TD) Выход
4 Готовность выходных данных (Data Terminal Ready, DTR) Выход
5 Сигнальное заземление (Signal Ground, SG) -
6 Готовность данных (Data Set Ready, DSR) Вход
7 Запрос для передачи (Request to send, RTS) Выход
8 Сброс для передачи (Clear to Send, CTS) Вход
9 Индикатор вызова (Ring Indicator, RI) Вход

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

Интерфейс RS-232-C определяет обмен между устройствами двух типов: DTE (Data Terminal Equipment - терминальное устройство) и DCE (Data Communication Equipment - устройство связи). В большинстве случаев, но не всегда, компьютер является терминальным устройством. Модемы, принтеры, графопостроители всегда являются устройствами связи. Рассмотрим теперь сигналы интерфейса RS-232-C более подробно.

5.2.1. Сигналы интерфейса RS-232-C

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

Входы TD и RD используются компьютером и модемом по-разному. Компьютер использует вход TD для передачи данных, а вход RD для приема данных. И наоборот, модем использует вход TD для приема, а вход RD для передачи данных.

Поэтому для соединения компьютера и модема выводы их разъемов необходимо соединить напрямую (см. рис. 5.2).

Рис. 5.2. Соединение компьютера и модема

Подтверждение связи

Рассмотрим процесс подтверждения связи между компьютером и модемом (handshake). В начале сеанса связи компьютер должен удостовериться, что модем может произвести вызов (находится в рабочем состоянии). Затем после вызова абонента модем должен сообщить компьютеру, что он соединился с удаленной системой. Подробнее это происходит следующим образом.

Компьютер устанавливает сигнал на линии DTR, чтобы показать модему, что он готов к проведению сеанса связи. В ответ модем устанавливает сигнал на линии DSR. Когда модем произвел соединение с другим удаленным модемом, он устанавливает сигнал на линии DCD, чтобы сообщить об этом компьютеру.

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

Управление потоком

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

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

Стандарт RS-232-C определяет возможность управления потоком только для полудуплексного соединения. Полудуплексным называется соединение, при котором в каждый момент времени данные могут передаваться только в одну сторону.

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

В полудуплексных соединениях компьютер подает сигнал RTS, когда ему надо передать данные. Модем отвечает сигналом по линии CTS, когда он готов, и компьютер начинает передачу данных. До тех пор, пока оба сигнала RTS и CTS не примут активное состояние, только модем может передавать данные.

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

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

Нуль-модем

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

Для соединения двух терминальных устройств - двух компьютеров - как минимум необходимо перекрестное соединение линий TR и RD.

Однако в большинстве случаев этого недостаточно, так как для устройств DTE и DCE функции, выполняемые линиями DSR, DTR, DCD, CTS и RTS, асимметричны.

Устройство DTE подает сигнал DTR и ожидает получения сигналов DSR и DCD. В свою очередь, устройство DCE подает сигналы DSR, DCD и ожидает получения сигнала DTR. Таким образом, если вы соедините вместе два устройства DTE кабелем, который вы использовали для соединения устройств DTE и DCE, то они не смогут договориться друг с другом. Не выполнится процесс подтверждения связи.

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

Чтобы решить все эти проблемы, для соединения двух устройств типа DTE используется специальный кабель, в обиходе называемый нуль-модемом.

Имея два разъема и кабель, вы легко можете спаять его самостоятельно, руководствуясь схемами, изображенными на рисунке 5.3.

Нуль-модемный кабель, представленный в левой части рисунка 5.3, на схеме 1, содержит значительно меньше проводов, чем нуль-модемный кабель, изображенный справа. За счет того, что на каждом конце кабеля линии RTS, CTS и DSR, DCD, DTR соединены вместе, процедуры подтверждения связи и управления потоком всегда будут заканчиваться успешно. На больших скоростях это может привести к потере информации, поэтому мы рекомендуем использовать вторую схему.

Рис. 5.3. Нуль-модем

Для полноты картины рассмотрим еще один аспект, связанный с механическим соединением портов RS-232-C. Из-за наличия двух типов разъемов - DB25 и DB9 - часто бывают нужны переходники с одного типа разъемов на другой. Например, вы можете использовать такой переходник для соединения COM-порта компьютера и кабеля нуль-модема, если на компьютере установлен разъем DB25, а кабель оканчивается разъемами DB9.

Схему такого переходника мы приводим на рисунке 5.4.

Рис. 5.4. Переходник для разъемов DB25 и DB9

Заметим, что многие модемы можно настроить таким образом, что они не будут проверять состояние сигналов DTR и RTS. Более подробно эти возможности описаны в разделе "Расширенный набор AT-команд".

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

На этапе инициализации системы, модуль процедуры начальной загрузки BIOS тестирует имеющиеся асинхронные порты RS-232-C и инициализирует их. В зависимости от версии BIOS инициализирует первые два или четыре порта. Их базовые адреса записываются в области данных BIOS начиная с адреса 0040:0000h.

Чтобы просмотреть значения, записанные в области данных BIOS вашего компьютера, можно воспользоваться программой Debug, поставляемой вместе с операционной системой MS-DOS. Таким образом, можно определить, какие COM-порты установлены на компьютере.

Запустите программу Debug. Для этого введите в строке системного приглашения MS-DOS команду DEBUG.EXE. Программа Debug выведет на экран приглашение в виде черточки '-'. Чтобы просмотреть содержимое оперативной памяти по адресу 0040:0000h введите команду d40:0 и нажмите клавишу <Enter>. На экране появится дамп памяти, начиная с адреса 0040:0000h до 0040:0080h.

-d40:0
0040:0000  F8 03 F8 02 E8 03 00 00-78 03 00 00 00 00 00 00   ........x.......
0040:0010  61 C6 00 80 02 80 00 20-00 00 38 00 38 00 E0 50   a...... ..8.8..P

Нас будут интересовать только первые восемь байт из этого дампа. Первые два байта содержат базовый адрес порта COM1. Поменяв местами два этих байта, получаем адрес 3F8h. Следующие два байта содержат адрес порта COM2 - 2F8h, затем COM3 - 3E8h. Два байта, соответствующие порту COM4, содержат нулевые значения. Это означает, что асинхронный последовательный адаптер компьютера не имеет порта COM4 или BIOS компьютера не может или не пытается его обнаружить.

Теперь вы можете завершить работу программы Debug. Для этого введите команду q и нажмите клавишу <Enter>.

Адреса COM-портов из нашего примера являются своего рода стандартом. Первый адаптер COM1 обычно имеет базовый адрес 3F8h и занимает диапазон адресов от 3F8h до 3FFh. Второй адаптер COM2 имеет базовый адрес 2F8h и занимает адреса 2F8h...2FFh. Третий адаптер COM3 имеет базовый адрес 3E8h и занимает диапазон адресов от 3E8h до 3EFh. Четвертый адаптер COM4 имеет базовый адрес 2E8h и занимает адреса 2E8h...2EFh. Тем не менее, для некоторых компьютеров, например, с шиной MCA - PS/2, адреса COM-портов могут иметь другие значения.

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

Теперь перейдем к подробному рассмотрению портов, используемых COM-портами.

5.3.1. Регистр данных

Регистр данных расположен непосредственно по базовому адресу COM-порта и используется для обмена данными и для задания скорости обмена.

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

В зависимости от состояния старшего бита управляющего регистра (расположенного по адресу base_adr + 3, где base_adr соответствует базовому адресу COM-порта) назначение этого регистра может изменяться. Если старший бит равен нулю, регистр используется для записи передаваемых данных. Если же старший бит равен единице, регистр используется для ввода значения младшего байта делителя частоты тактового генератора. Изменяя содержимое делителя, можно изменять скорость передачи данных. Старший байт делителя записывается в регистр управления прерываниями по адресу base_adr + 1.

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

Делитель, десятичная форма Делитель, шестнадцатиричная форма Скорость передачи, бит за секунду
1040 600h 110
768 300h 150
384 180h 300
192 0C0h 600
96 60h 1200
48 30h 2400
24 18h 4800
12 0Ch 9600
8 8h 14400
6 6h 19200
3 3h 38400
2 2h 57600
1 1h 115200

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

5.3.2. Регистр управления прерываниями

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

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

Для удобства доступа к регистрам UART мы определили для каждого регистра соответствующее объединение (см. файл UART_REG.H).

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

// Смещение относительно базового адреса
#define ICR_N            1

// Регистр управления прерываниями
typedef union _ICR_
{
      struct
      {
             unsigned char in_ready         : 1;
             unsigned char out_ready            : 1;
             unsigned char err                          : 1;
             unsigned char change                  : 1;
             unsigned char reserv             : 4;
      } bit_reg;

      unsigned char byte;
} ICR;

5.3.3. Регистр идентификации прерывания

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

Формат регистра представлен ниже:

Значение бит
D2 D1
Причина прерывания
00 Изменилось состояние модема. Устанавливается при изменении состояния входных линий CTS, RI, DCD, DSR. Сбрасывается после чтения состояния модема из регистра состояния модема
01 Буфер передатчика пуст. Сбрасывается при записи новых данных в регистр данных
10 Данные приняты и доступны для чтения. Сбрасывается после чтения данных из регистра данных
11 Прерывание по линии состояния приемника, возникает при переполнении приемника, ошибках четности или формата данных или при состоянии BREAK. Сбрасывается после чтения состояния линии из регистра состояния линии

В файле UART_REG.H регистр идентификации прерывания определен следующим образом:

// Смещение относительно базового адреса
#define IIDR_N    2

// Регистр идентификации прерывания
typedef union _IIDR_
{
      struct
      {
             unsigned char no_inter           : 1;
             unsigned char inter_id           : 2;
             unsigned char reserv             : 5;
      } bit_reg;

      unsigned char byte;
} IIDR;

5.3.4. Управляющий регистр

Управляющий регистр доступен для записи и чтения. Этот регистр управляет различными характеристиками UART: скоростью передачи данных, контролем четности, передачей сигнала BREAK, длиной передаваемых слов (символов).

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

// Смещение относительно базового адреса
#define LCR_N    3

// Управляющий регистр
typedef union _LCR_
{
      struct
      {
             unsigned char len                 : 2;
             unsigned char stop                : 1;
             unsigned char parity              : 2;
             unsigned char stuck_parity        : 1;
             unsigned char en_break_ctl        : 1;
             unsigned char dlab                : 1;
      } bit_reg;

      unsigned char byte;
} LCR;

5.3.5. Регистр управления модемом

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

Регистр управления модемом определен нами в файле UART_REG.H следующим образом:

// Смещение относительно базового адреса
#define MCR_N   4

// Регистр управления модемом
typedef union  _MCR_
{
      struct
      {
             unsigned char dtr          : 1;
             unsigned char rts          : 1;
             unsigned char out1         : 1;
             unsigned char out2         : 1;
             unsigned char diag         : 1;
             unsigned char reserv       : 3;
      } bit_reg;

      unsigned char byte;
} MCR;

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

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

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

// Смещение относительно базового адреса
#define LSR_N     5

// Регистр состояния линии
typedef union _LSR_
{
      struct
      {
             unsigned char in_ready             : 1;
             unsigned char overflow             : 1;
             unsigned char parity               : 1;
             unsigned char synxr                : 1;
             unsigned char break_detect : 1;
             unsigned char out_ready            : 1;
             unsigned char shift_ready          : 1;
             unsigned char taimout              : 1;
      } bit_reg;

      unsigned char byte;
} LSR;

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

Регистр состояния модема позволяет программе определить состояние управляющих сигналов, передаваемых модемом асинхронному порту компьютера. Формат регистра состояния модема представлен ниже:

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

// Смещение относительно базового адреса
#define MSR_N   6

// Регистр состояния модема
typedef union _MSR_
{
      struct
      {
             unsigned char change_cts  : 1;
             unsigned char change_dsr  : 1;
             unsigned char change_ri   : 1;
             unsigned char change_dcd  : 1;
             unsigned char cts         : 1;
             unsigned char dsr         : 1;
             unsigned char ri          : 1;
             unsigned char dcd         : 1;
      } bit_reg;

      unsigned char byte;
} MSR;

5.4. Современные микросхемы UART

Микросхема UART 8250 в ее исходном виде использовалась только в старых моделях компьютеров IBM PC и IBM XT. Современные микросхемы - UART 16450, 16550 и 16550A, изготовленные по новой технологии, позволяют достичь более высокой скорости обмена данными, а также обладают новыми аппаратными возможностями. В этой главе мы рассмотрим основные различия между 8250 и новыми микросхемами, а также приведем дополнительную информацию по программированию UART 16550A.

Опишем основные возможности различных микросхем UART:

5.4.1. Как определить тип микросхемы UART

Как же определить, какая из этих микросхем UART установлена на вашем асинхронном адаптере? Кроме возможности заглянуть в документацию, существует еще один способ. Фактически этот способ основан на различиях в особенностях микросхем UART. Ниже приведены особенности микросхем UART различных типов:

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

5.4.2. Программа для определения типа микросхемы UART

Теперь приведем программу TST_UART, реализующую изложенный алгоритм. В данной программе используются созданные нами функции is_UART_8250() и is_UART_FIFO(). Первая позволяет определить по отсутствию регистра расширения микросхему UART 8250, а вторая по особенностям реализации внутреннего буфера данных различает остальные типы микросхем.

Исходный текст программы TST_UART представлен в листинге 5.1.

Листинг 5.1. Файл TST_UART.C

// Программа определения типа микросхемы UART асинхронного
// последовательного адаптера

#define    UART_8250     1
#define    UART_16450    2
#define    UART_16550    3
#define    UART_16550A   4

void main(void) {

      // Номер асинхронного порта может быть 0 для COM1
      // или 1 для COM2

      int port = 0;
      int test;

      printf("\n(c) Frolov G.V. 1992-1994.   "
                "Программа определения типа UART\n\n");

      printf(  "\Введите номер асинхронного"
                                "порта (COM1 - 0, COM2 - 1):");

      scanf( "%d", &port );
      if(( port != 0 ) && ( port != 1 )){
             printf( "асинхронный порт COM%d не поддерживается\n",
                                      port );
             exit( -1 );
      }

      // Проверяем, является ли микросхема UART - UART 8250
      if( is_UART_8250(port) == UART_8250 ) {
             printf("Обнаружена микросхема UART 8250\n");
             exit(0);
      }

      // Проверяем другие типы микросхем UART
      if(( test = is_UART_FIFO(port) ) == UART_16550A ) {
             printf("Обнаружена микросхема UART 16550A\n");
             exit(0);
      }

      else if(test == UART_16550) {
             printf("Обнаружена микросхема UART 16550\n");
             exit(0);
      }

      printf("Обнаружена микросхема UART 16450\n");
}

/**
*.Name         is_UART_8250
*
*.Descr        Функция определяет тип микросхемы,
*              используемый данным последовательным асинхронным
*              адаптером (UART).
*
*.Proto        int is_UART_8250( int port );
*
*.Params       int port - номер асинхронного адаптера:
*                 0 - COM1, 1 - COM2
*
*.Return       Для UART 8250 - возвращает константу UART_8250,
*              в остальных случаях возвращает 0
**/
int is_UART_8250( int port ) {

      int save_scr, in_scr;

      // Сохраняем значения регистра расширения
      save_scr = inp( 0x3ff - 0x100 * port );

      // Записываем в регистр расширения число 0x5A
      outp( 0x3ff - 0x100 * port, 0x5A );

      // Считываем регистр расширения
      in_scr = inp( 0x3ff - 0x100 * port  );

      // Сохранилось ли записанное число?
      if( in_scr != 0x5A ) {

             // Если нет, значит, регистр расширения отсутствует и,
             // следовательно, тип микросхемы - UART 8250

             // Восстанавливаем значение регистра расширения
             outp( 0x3ff - 0x100 * port, save_scr );
             return( UART_8250 );
      }

      // Записываем в регистр расширения другое число - 0xA5
      outp( 0x3ff - 0x100 * port, 0xA5 );

      // Считываем регистр расширения
      in_scr = inp( 0x3ff - 0x100 * port  );

      // Восстанавливаем значение регистра расширения
      outp( 0x3ff - 0x100 * port, save_scr );

      // Сохранилось ли записанное число?
      if( in_scr != 0xA5 )

             // Если нет, регистр расширения отсутствует и,
             // следовательно, тип микросхемы - UART 8250
             return( UART_8250 );

      // В противном случае регистр расширения есть и надо
      // выполнить дальнейшее тестирование для определения
      // типа UART
      return( 0 );
}

/**
*.Name         is_UART_FIFO
*
*.Descr        Функция определяет тип микросхемы,
*              используемой данным последовательным асинхронным
*              адаптером (UART).
*
*.Proto        int is_UART_FIFO( int port );
*
*.Params       int port - номер асинхронного адаптера:
*                 0 - COM1, 1 - COM2
*
*.Return       для UART 164550 возвращает константу UART_16450,
*              для UART 16550  возвращает константу UART_16550,
*              для UART 16550A возвращает константу UART_16550A
**/
int is_UART_FIFO( int port ) {

      int save_iir, in_iir;

      // Сохраняем значение регистра определения прерывания
      save_iir = inp( 0x3fa - 0x100 * port );

      // Разрешаем использование FIFO
      outp( 0x3fa - 0x100 * port, 0x1 );

      // Читаем значение регистра определения прерывания
      in_iir = inp( 0x3fa - 0x100 * port  );

      // Восстанавливаем  значение регистра определения прерывания
      outp( 0x3fa - 0x100 * port, 0x0 );

      // Если бит D6 содержит единицу, значит, мы имеем UART 16550A
      if(( in_iir & 0x40 ) == 1 )
             return( UART_16550A );

      // Если бит D7 содержит единицу, значит, мы имеем UART 16550
      if(( in_iir & 0x80 ) == 1 )
             return( UART_16550 );

      // Если биты D7 и D6 содержат нули, значит, мы имеем UART
      // 16450 (буфер FIFO отсутствует)
      return( UART_16450 );
}

5.4.3. Изменения в регистрах UART 16550A

В этой главе мы рассмотрим изменения в формате регистров UART 16550A по сравнению с UART 8250.

Начнем с регистра идентификации прерывания. Этот регистр доступен только для чтения. По сравнению с UART 8250 в нем добавлены два бита - D6 и D7, которые показывают состояние буфера FIFO.

Если биты D7 и D6 оба равны единице, то разрешено использование буферизации (FIFO). Если же только бит D7 содержит единицу, это означает, что вы имеете дело с микросхемой UART 16550. В ней режим буферизации реализован с ошибками, и использовать его не надо.

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

Если бит D3 содержит единицу, то бит D2 также содержит единицу. Это означает, что буфер приемника содержит данные.

Для микросхем UART 8250 и 16450 биты D3, D6 и D7 всегда содержат нули. Биты D4 и D5 не используются во всех рассматриваемых микросхемах.

Для управления режимом буферизации UART 16550A имеет дополнительный регистр - регистр управления буферизацией FIFO. Этот регистр разделяет общий адрес с регистром идентификации прерываний - base_adr + 2. Но в отличие от регистра идентификации прерываний, доступного только для чтения, этот регистр доступен только для записи.

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

Биты D7 D6 Количество символов, байт
00 1
01 4
10 8
11 14

5.4.4. Как использовать буферизацию?

Обычно без использования буферизации UART генерирует прерывание всякий раз, когда передается или принимается очередной символ. В результате при скорости 2400 бит/с прерывания происходят с частотой 240 прерываний за одну секунду. Это не очень много, но при увеличении скорости до максимально возможной - 115200 бит/с за секунду - происходит уже 11520 прерываний. 11520 прерываний за одну секунду - это уже много. Использование буферизации позволяет при той же скорости резко сократить количество прерываний. Так, при генерации прерываний каждые 14 символов (бит регистра управления буферизацией D7 = 1, D6 = 1) за секунду произойдет около 823 прерываний.

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

При программировании UART 16550A для использования режима буферизации необходимо выполнить следующие действия:

Операционная система Windows 3.1 поддерживает новые возможности асинхронных последовательных адаптеров, созданных на основе микросхемы UART 16550A. Поэтому, когда вы пишете приложение для Windows, нет необходимости самостоятельно определять тип микросхемы UART.

5.5. Первая телекоммуникационная программа

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

Программа работает с асинхронным адаптером COM1. Для правильной работы необходимо замкнуть вместе контакты 2 и 3 разъема COM1. В этом случае данные передаваемые в COM-порт сразу же принимаются им обратно.

Главный файл программы AUX_TEST представлен в листинге 5.2.

Листинг 5.2. Файл AUX_TEST.C

#include <stdio.h>
#include <conio.h>
#include "sysp_com.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);

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

      for(;;) {

             // Вводим символ с клавиатуры и передаем его
             // в асинхронный адаптер
             aux_outp(getch(), 0);

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

             putchar(aux_inp(0));
      }
}

После запуска программы AUX_TEST вызывается функция aux_stat, определенная в нашем приложении. Она заполняет структуру amd типа AUX_MODE, записывая в нее информацию о текущем режиме COM-порта.

Информация, записанная в этой структуре, отображается на экране. Изменяем поле baud структуры amd, содержащее значение скорости передачи данных, записывая в нее число 115200. Затем функция aux_init, определенная в программе, увеличивает скорость передачи информации до 115200 бит/с.

Затем начинает выполняться бесконечный цикл, в котором вводится символ с клавиатуры и передается в COM-порт:

aux_outp(getch(), 0);

Сразу после передачи кода нажатой клавиши в COM-порт, вводим символ из этого же порта и отображаем его на экране:

putchar(aux_inp(0));

Чтобы завершить работу программы, достаточно нажать комбинацию клавиш <Ctrl+C>.

Включаемый файл SYSP.H, представленный в листинге 5.3, содержит описания функций aux_stat, aux_init, aux_inp, aux_outp и определение структуры AUX_MODE.

Листинг 5.3. Файл SYSP.H

void aux_stat(AUX_MODE *mode, int port);
int  aux_init(AUX_MODE *mode, int port, int imask);
void aux_outp(char chr, int port);
char aux_inp(int port);

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;

Теперь перейдем к рассмотрению функций aux_stat, aux_init, aux_inp и aux_outp, определенных в программе AUX_TEST.

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

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

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

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

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

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

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

Листинг 5.4. Файл AUX_STAT.C

/**
*.Name         aux_stat
*
*.Descr        Функция считывает текущий режим
*              асинхронного порта и записывает его
*              в структуру с типом AUX_MODE.
*
*.Proto        void aux_stat(AUX_MODE *mode, int port);
*
*.Params       AUX_MODE mode - структура, описывающая
*              протокол и режим работы порта
*
*              int port - номер асинхронного адаптера:
*                 0 - COM1, 1 - COM2
*
*.Return       Ничего
**/

#include <stdio.h>
#include <conio.h>
#include "sysp_com.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 для изменения текущего режима COM-порта. Исходный текст этой функции можно найти в листинге 5.5.

Листинг 5.5. Файл AUX_INIT.C

/**
*.Name         aux_init
*
*.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_com.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);
}

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

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

Признаком того, что регистр передатчика свободен, является установленный в 1 бит 5 регистра состояния линии с адресом base_adr + 5. Функция aux_outp (см. листинг 5.6) ждет окончания передачи текущего символа, затем посылает в асинхронный адаптер следующий символ.

Листинг 5.6. Файл AUX_OUTP.C

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

#include <stdio.h>
#include <conio.h>
#include "sysp_com.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);
}

5.5.3. Прием данных

Аналогично передаче данных перед вводом символа из регистра данных (адрес base_adr) необходимо убедиться в том, что бит 0 регистра состояния линии (адрес base_adr + 5) установлен в 1. Это означает, что символ принят из линии и находится в буферном регистре приемника.

Для приема данных мы используем функцию aux_inp (см. листинг. 5.7).

Листинг 5.7. Файл AUX_INP.C

/**
*.Name         aux_inp
*
*.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_com.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));
}

5.6. Обработка прерываний COM-порта

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

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

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

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

Так следует поступать до тех пор, пока бит 0 регистра идентификации прерывания не станет равным нулю.

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

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

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

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

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

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

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

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

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

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

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

Прерыванию с номером 0 соответствует адрес 0000:0000, прерыванию с номером 1 - 0000:0004 и т. д.

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

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

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

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

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

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

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

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

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

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

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

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

Номер Описание
08h IRQ0 - прерывание интервального таймера, возникает 18,2 раза в секунду
09h IRQ1 - прерывание от клавиатуры. Генерируется при нажатии и при отжатии клавиши. Используется для чтения данных с клавиатуры
0Ah IRQ2 - используется для каскадирования аппаратных прерываний в машинах класса AT
70h IRQ8 - прерывание от часов реального времени
71h IRQ9 - прерывание от контроллера EGA
72h IRQ10 - зарезервировано
73h IRQ11 - зарезервировано
74h IRQ12 - зарезервировано
75h IRQ13 - прерывание от математического сопроцессора
76h IRQ14 - прерывание от контроллера жесткого диска
77h IRQ15 - зарезервировано
0Bh IRQ3 - прерывание асинхронного порта COM2
0Ch IRQ4 - прерывание асинхронного порта COM1
0Dh IRQ5 - прерывание от контроллера жесткого диска для XT
0Eh IRQ6 - прерывание генерируется контроллером флоппи-диска после завершения операции
0Fh IRQ7 - прерывание принтера. Генерируется принтером, когда он готов к выполнению очередной операции

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

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

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

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

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

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

Если контроллеры 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, - команды инициализации и команды операции. Возможны следующие операции:

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

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

В листинге 5.8 приведен пример программы LOCKFDD, маскирующей прерывание от флоппи-диска.

Листинг 5.8. Файл LOCKFDD.C

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

void main(void);

void main(void) {

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

      exit(0);
}

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

Листинг 5.9. Файл UNLOCK.C

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

void main(void);

void main(void) {

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

      exit(0);
}

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

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

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

Биты байта команды
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 может использоваться резидентными программами при проверке возможности своей активизации - можно проверить, не выполняется ли в настоящий момент обработка какого-нибудь прерывания, которая может конфликтовать с действиями резидентной программы.

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

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

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

На входе:  AH = 00h;
           DX = номер порта:   0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4;
           AL = параметры инициализации (см. ниже).
На выходе: AH = состояние порта асинхронного адаптера;
           AL = состояние модема.

При вызове этой функции регистр AL должен содержать параметры инициализации (x - состояние бита безразлично):

Биты Смысл
D1 D0 Длина слова в битах: 00 - 5 бит; 01 - 6 бит; 10 - 7 бит; 11 - 8 бит
D2 Количество стоповых бит: 0 - 1 бит; 1 - 2 бита
D4 D3 Четность: x0 - контроль на четность не используется; 01 - контроль на нечетность; 11 - контроль на четность
D7-D5 Скорость передачи данных: 000 - 110 бит/с 001 - 150 бит/с 010 - 300 бит/с 011 - 600 бит/с 100 - 1200 бит/с 101 - 2400 бит/с 110 - 4800 бит/с 111 - 9600 бит/с

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

Бит Смысл
D0 Тайм-аут, если установлен этот бит, другие биты не имеют значения
D1 Регистр сдвига передатчика пуст
D2 Буферный регистр передатчика пуст
D3 Обнаружено состояние BREAK
D4 Ошибка синхронизации
D5 Ошибка четности
D6 Ошибка переполнения входного регистра
D7 Данные готовы

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

Бит Смысл
D0 Линия CTS изменила состояние
D1 Линия DSR изменила состояние
D2 Линия RI изменила состояние
D3 Линия DCD изменила состояние
D4 Состояние линии CTS
D5 Состояние линии DSR
D6 Состояние линии RI
D7 Состояние линии DCD

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

На входе:  AH = 01h;
           DX = номер порта:   0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4;
           AL = передаваемый байт.
На выходе: AL сохраняется;
           AH = состояние порта асинхронного адаптера,
             если бит 7 регистра AH установлен в 1,
             произошла ошибка.

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

На входе:  AH = 02h;
           DX = номер порта:
              0 - COM1,
              1 - COM2,
              2 - COM3,
              3 - COM4;
На выходе: AL = принятый байт;
           AH = состояние порта асинхронного адаптера,
             если регистр AH не равен 0,
             произошла ошибка.

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

На входе:  AH = 03h;
           DX = номер порта:
              0 - COM1,
              1 - COM2,
              2 - COM3,
              3 - COM4;
На выходе: AH = состояние порта асинхронного адаптера;
           AL = состояние модема.

5.8. Поддержка асинхронного адаптера в MS-DOS

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

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

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

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

5.9. Стандартные функции библиотеки Си

Библиотеки трансляторов Borland C++ и Turbo C содержат две функции управления портами асинхронного последовательного адаптера - bioscom и _bios_serialcom. Обе эти функции обладают одинаковыми возможностями, но функция _bios_serialcom совместима с функцией _bios_serialcom из библиотек трансляторов фирмы Microsoft. Функция bioscom помечена в документации как устаревшая и оставлена для совместимости с ранними версиями трансляторов фирмы Borland.

Функции _bios_serialcom и bioscom управляют асинхронным последовательным портом компьютера через прерывание BIOS INT 0x14. Вследствие этого функция bioscom может не работать со скоростями больше чем 9600 бит/сек. Если вам нужны программы, обеспечивающие более высокие скорости, необходимо использовать непосредственное программирование портов асинхронного последовательного адаптера.

Рассмотрим функцию _bios_serialcom более подробно. Она объявлена в файле BIOS.H следующим образом:

unsigned _bios_serialcom(unsigned service,
                         unsigned serial_port,
                         unsigned data);

Первый аргумент функции - serial_port - определяет номер порта. Для порта COM1 этот аргумент должен быть равен 0, для COM2 - 1 и так далее.

Второй аргумент - service - определяет производимое функцией действие и может содержать одну из следующих констант:

Константа Назначение
_COM_INIT Инициализация последовательного порта
_COM_RECEIVE Принять байт
_COM_SEND Передать байт
_COM_STATUS Определить состояние порта

Назначение третьего аргумента функции - data - зависит от значения аргумента service. Если аргумент service установлен на _COM_RECEIVE или _COM_STATUS, то значение аргумента data безразлично. Если аргумент service установлен как _COM_INIT, то этот аргумент может состоять из одного или нескольких констант, объединенных булевой операцией ИЛИ. Данные константы приведены в следующей таблице:

Константа Назначение
_COM_CHR7 Передавать семь битов на символ
_COM_CHR8 Передавать восемь битов на символ
_COM_STOP1 Использовать один стоповый бит
_COM_STOP2 Использовать два стоповых бита
_COM_NOPARITY Не выполнять проверки на четность
_COM_EVENPARITY Выполнять проверку на четность
_COM_ODDPARITY Выполнять проверку на нечетность
_COM_110 Установить скорость 110 бит/с
_COM_150 Установить скорость 150 бит/с
_COM_300 Установить скорость 300 бит/с
_COM_600 Установить скорость 600 бит/с
_COM_1200 Установить скорость 1200 бит/с
_COM_2400 Установить скорость 2400 бит/с
_COM_4800 Установить скорость 4800 бит/с
_COM_9600 Установить скорость 9600 бит/с

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

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

Назначение отдельных бит старшего байта представлено в следующей таблице:

Бит Если бит установлен
15 Исчерпан лимит времени (тайм-аут)
14 Регистр сдвига передатчика свободен (пуст)
13 Регистр передатчика свободен (пуст)
12 Произошел разрыв связи (состояние BREAK)
11 Ошибка в управляющих битах (ошибка синхронизации)
10 Ошибка четности
9 Ошибка переполнения
8 Данные готовы

Когда аргумент service равен _COM_SEND, бит 15 устанавливается в единицу, если данные не могут быть переданы.

Если аргумент service равен _COM_RECEIVE и чтение байта произошло успешно, он находится в младшем байте возвращаемого функцией значения. Если чтение произошло с ошибками, это отражается битами 9, 10, 11 или 15.

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

Бит Значение
7 Состояние линии DCD
6 Состояние линии RI
5 Состояние линии DSR
4 Состояние линии CTS
3 Линия DCD изменила состояние
2 Линия RI изменила состояние
1 Линия DSR изменила состояние
0 Линия CTS изменила состояние

Приведем небольшой пример использования функции. В этом примере функция _bios_serialcom() сначала инициализирует последовательный порт, а затем передает символы, набранные на клавиатуре в порт, а символы, считанные из порта, - на экран компьютера (см. листинг 5.10).

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

Листинг 5.10. Файл SERIAL.C

// Программа иллюстрирует доступ к последовательному порту
// через функцию _bios_serialcom()

#include <bios.h>            // необходимо включить при
                             // использовании _bios_serialcom()
#include <stdio.h>

#define COM1 0               // первый последовательный порт
#define DATA_READY 0x100     // данные приняты и готовы для
                             // чтения
int main(void) {

      unsigned in, out, status;

      // Инициализируем последовательный порт
      // устанавливаем скорость 1200 бит/с, 8 битов на символ, один
      // стоповый бит
      _bios_serialcom( _COM_INIT, COM1, _COM_1200 |
                                        _COM_CHR8 | _COM_STOP1);

      printf("\n\n Для выхода нажмите клавишу [ESC]\n");

      for(;;)  {

             // Определяем состояние последовательного порта
             status = _bios_serialcom(_COM_STATUS, COM1, 0);

             // Если данные готовы, считываем их из
             // последовательного порта и выводим на экран  дисплея
             if(status & DATA_READY)
                   if((out = _bios_serialcom(_COM_RECEIVE, COM1, 0) &
                     0x7F) != 0)
                         putch(out);

             // Проверяем, не нажата ли клавиша на клавиатуре?
             if(kbhit()) {

                   // Если нажата клавиша [ESC] выходим из программы
                   if((in = getch()) == 0x1b)
                         break;

                   // В противном случае передаем код нажатой клавиши
                   // на асинхронный последовательный порт
                   _bios_serialcom(_COM_SEND, COM1, in);
             }
      }
      return(0);
}

Теперь рассмотрим функцию bioscom() из библиотеки трансляторов Borland C++ и Turbo C. Эта функция отмечена в документации на Borland C++ версии 4.0, как устаревшая:

int bioscom(int service, char data, int serial_port);

Функция аналогична функции _bios_serialcom(), за исключением следующих моментов:

Рассмотрим подробнее аргументы функции bioscom(). Первый аргумент функции - serial_port - определяет номер порта. Для COM1 этот аргумент должен быть равен 0, для COM2 - 1 и так далее.

Назначение второго аргумента функции - data - зависит от значения аргумента service. Если аргумент service равен единице (_COM_RECEIVE) или тройке (_COM_STATUS), то значение аргумента data безразлично. Если аргумент service равен нулю (_COM_INIT), то этот аргумент может состоять из одного или нескольких битовых полей (констант), объединенных булевой операцией ИЛИ (|). Данные константы приведены в следующей таблице:

Константа Значение
0x02 (_COM_CHR7) Передавать семь битов на символ (байт)
0x03 (_COM_CHR8) Передавать восемь битов на символ
0x00 (_COM_STOP1) Использовать один стоповый бит
0x04 (_COM_STOP2) Использовать два стоповых бита
0x00 (_COM_NOPARITY) Не проводить проверки на четность
0x18 (_COM_EVENPARITY) Проводить проверку на четность
0x08 (_COM_ODDPARITY) Проводить проверку на нечетность
0x00 (_COM_110) Установить скорость 110 бит/с
0x20 (_COM_150) Установить скорость 150 бит/с
0x40 (_COM_300) Установить скорость 300 бит/с
0x60 (_COM_600) Установить скорость 600 бит/с
0x80 (_COM_1200) Установить скорость 1200 бит/с
0xa0 (_COM_2400) Установить скорость 2400 бит/с
0xc0 (_COM_4800) Установить скорость 4800 бит/с
0xe0 (_COM_9600) Установить скорость 9600 бит/с

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

Третий аргумент - service - может принимать следующие значения:

Константа Значение
0 (_COM_INIT) Инициализация последовательного порта
1 (_COM_RECEIVE) Принять байт
2 (_COM_SEND) Передать байт
3 (_COM_STATUS) Определить состояние порта

Аналогично функции _bios_serialcom() функция bioscom() возвращает 16-битовое целое число. В старшем байте возвращаемого значения содержатся биты, определяющие состояние последовательного порта. Содержимое младшего байта зависит от значения параметра service, с которым вызывалась функция.

Возможные значения для старшего байта представлены в следующей таблице:

Бит Если бит установлен
15 Исчерпан лимит времени (тайм-аут)
14 Регистр сдвига передатчика свободен (пуст)
13 Регистр передатчика свободен (пуст)
12 Произошел разрыв связи (состояние BREAK)
11 Ошибка в управляющих битах (ошибка синхронизации)
10 Ошибка четности
9 Ошибка переполнения
8 Данные готовы

Когда аргумент service равен _COM_SEND, бит 15 устанавливается в единицу, если данные не могут быть переданы.

Если аргумент service равен _COM_RECEIVE и чтение байта произошло успешно, принятый байт находится в младшем байте возвращаемого функцией значения. Если чтение произошло с ошибками, они конкретизируются битами 9, 10, 11, или 15.

Если атрибут service равен _COM_INIT или _COM_STATUS, биты младшего байта используются следующим образом:

Бит Значение
7 Состояние DCD линии
6 Состояние RI линии
5 Состояние DSR линии
4 Состояние CTS линии
3 Линия DCD изменила состояние
2 Линия RI изменила состояние
1 Линия DSR изменила состояние
0 Линия CTS изменила состояние