void interrupt far s_inthndlr(void)
{
int c;
register int int_id, intmask;
/* прерывания разрешаются немедленно */
_enable();
while (TRUE)
{
/* чтение регистра идентификации прерываний, IIR */
int_id = inp(IIR);
if (bit0(int_id) == 1)
- 8-20 -
{
/* если бит 0 равен 1, тогда прерывания не поступают. послать
* сигнал конец прерывания программируемому контроллеру
* прерываний 8259A и затем вернуться.
*/
outp(P8259_0, END_OF_INT);
return;
}
/* если есть прерывание получения данных, разрешить прерывания
* для "свободен регистр хранения передачи"
*/
if (int_id) >= RXDATAREADY)
turnon_int(THEREINT,intmask);
/* обработать прерывание в соответствии с идентификатором.
* Следующий список составлен согласно возрастанию приоритета.
*/
switch (int_id)
{
case MDMSTATUS: /* состояние готовности модема */
.
.
.
break;
case TXREGEMPTY: /* послать символ */
.
.
.
break;
case RXDATAREADY: /* читать символ */
.
.
.
break;
case RLINESTATUS: /* читать состояние линии */
.
.
.
break;
/* пропустить, если идентификатор не является одним из
* перечисленных */
}
}
}
Обратите внимание, что мы воспользовались ключевым словом
interrupt,имеющемся в Microsoft C 5.0 и позволяющем писать обра-
ботчик на языке Си. Это ключевое слово используется как специфи-
катор функции, которую Вы желаете установить в качестве обработ-
чика прерываний определенного номера прерывания. При трансляции
функции с атрибутом interrupt компилятор генерирует код для пер-
воначального помещения в стек регистров AX, CX, DX, BX, SP, BP,
SP, SI, DI, DS и ES. Затем он устанавливает регистр DS в режим
ссылки на сегмент данных указанной функции. После этой начальной
последовательности следует код функции. В заключении компилятор
использует команду IRET вместо обычной команды RET для выхода из
функции. Данный пример является типичным для использования атри-
- 8-21 -
бута interrupt. В компиляторе Turbo C также имеется это ключевое
слово, но помещение регистров в стек происходит в другом порядке.
Когда Вы пишете обработчик прерываний на языке Си, то необхо-
димо соблюдать такие же предосторожности, как и при написании об-
работчиков на языке ассемблера. Например, Вы не должны использо-
вать какую-либо библиотечную подпрограмму, вызывающую функцию DOS
(доступ к ним осуществляется посредством команды прерывания 21h).
Такими функциями в языке Си являются подпрограммы ввода/вывода
файлов. С другой стороны, подпрограммы, находящиеся в категории
подпрограмм манипулирования строками, хранятся в функции
interrupt.
Очереди обработчика прерываний
Целью обработчика прерываний последовательного порта является
скорейшее сохранение поступающих символов. Наилучшим образом это
достигается с использованием буфера. Прикладная программа может
восстанавливать из этого буфера символы со своей скоростью, не
беспокоясь о потере какого-либо символа, потому что она работает
недостаточно быстро. Посылаемые символы также могут направляться
обработчику прерываний через другой буфер.
Каждый из этих буферов должен вести себя как линия проверки
регистра. Символы поступают в линию один за другим и программа,
считывающая символы, принимает первый из них и обрабатывает его,
затем принимает следующий и так далее. Буфер такого типа известен
как "первый пришел - первый вышел" (FIFO) буфер. Это также назы-
вается очередью.
На рисунке 8-8 показана концептуальная реализация очереди.
Очередь, естественно, имеет начало и конец. В реальной реализации
размер очереди, т.е. максимального числа символов, которое она
может содержать, фиксировано. Удобно считать ячейки памяти, свя-
занные с очередью, кругом, таким образом, что пройдя последнюю
ячейку, мы возвращаемся к первой. Это делает эффективным исполь-
зование ограниченного пространства, доступного для очереди. Такая
реализация очереди называется циклической.
ЪДДДВДДДВДДДВДДДВДДДВДДД¬
¦ ¦ ¦ ¦ ¦ ¦ ¦
ГДДД†ДДДБДДДБДДДБДДД†ДДДґ
¦ ¦ ¦ ¦
ГДДДґ ГДДДґ
¦ ¦Задние Первые¦ ¦
ГДДД†ДДД¬ ЪДДД†ДДДґ
¦ ¦ ¦Д¬ ЪД¦ ¦ ¦
АДДДБДДДЩ ¦ ¦ АДДДБДДДЩ
¦ ¦
Вход Выход
Рис.8-8. Циклический буфер FIFO (очередь)
Уборка перед закрытием магазина
После того, как Ваша прикладная программа перестает нуждаться
в дальнейшем последовательном вводе/выводе, необходимо восстано-
вить порт в его обычное состояние. Восстановление включает в себя
установку всех битов регистра разрешения прерываний порта в ноль
и выключение всех сигналов управления модемом. Затем контроллер
- 8-22 -
8259A должен быть запрограммирован для прекращения приема преры-
ваний последовательного порта. В заключение, вектор последова-
тельного прерывания необходимо сбросить в начальное значение, ко-
торое было сохранено при инициализации ввода/вывода. Вот как это
реализуется в Microsoft C 5.0:
int intmask;
.
.
.
/* Запретить прерывания на время очистки */
_disable();
/* Сначала сбросить регистр разрешения прерываний порта */
outp(IER,IEROFF);
/* Выключить все биты регистра управления модемом */
outp(MCR,MCROFF);
/* Затем запретить распознавание контроллером 8259A прерываний
последовательного порта */
intmask = inp(P8259_1) | int_disable_mask;
outp(P8259_1, intmask);
/* Восстановить первоначальный вектор прерываний */
_dos_setvect(int_number, old_handler);
/* Снова разрешить прерывания */
_enable();
Пример программы
Мы описали аппаратные средства последовательного порта, указа-
ли, какие действия необходимо выполнить для программирования порта
в целях организации эффективного управляемого прерываниями вво-
да/вывода. Осталось только объединить отдельные части, для того,
чтобы показать, каким образом создается завершенная коммуникацион-
ная программа. Мы делаем это в листинге 8-1, который содержит
основную коммуникационную программу, написанную на Microsoft C
версии 5.0.
Листинг 8-1. Коммуникационная программа на Microsoft C 5.0
------------------------------------------------------------------
/*
* Имя файла: SERIO.C
* Цель: Иллюстрация программирования
* последовательного порта в систе-
* мах MS-DOS. Эта версия разрабо-
* тана на персональном компьтере
* IBM PC-AT с последовательным
* адаптером фирмы IBM.
* Использовалась операционная
* система DOS 3.1.
* Автор: Наба Баркакати, март 1988
* Язык: Microsoft C 5.0
* Модель памяти: Большой емкости
* Транслировать/компоновать: CL /AL /Gs serio.c
*/
- 8-23 -
/*----------------------------------------------------------*/
#include
#include
#include
#include
#include
#define TRUE 1
#define FALSE 0
#define EOS '\0'
#define CONTROL(x) (x-0x40)
#define ESC_KEY CONTROL('[')
/* Определить коммуникационные параметры */
#define COM_PARAMS (_COM_CHR8 | _COM_STOP1 | \
_COM_NOPARITY |_COM_1200)
/* Определить размеры приемного и передающего буферов */
#define RXQSIZE 512
#define TXQSIZE 512
/* Определения для программируемого контроллера
* прерываний 8259
*/
#define P8259_0 0x20 /* регистр управления прерыванием */
#define P8259_1 0x21 /* регистр маски прерывания */
#define END_OF_INT 0x20 /* не определенный EIO */
/* Определить коды ASCII XON и XOFF */
#define XON_ASCII (0x11)
#define XOFF_ASCII (0x13)
/* Обратиться к области данных BIOS по адресу 400h */
#define BIOS_DATA ((int far *)(0x400000L))
/* Адресом коммуникационного порта является короткое целое
* 'comport'. Эта переменная инициализируется чтением из
* области данных BIOS на сегменте 0х40.
*/
#define IER (comport + 1) /* регистр разрешения прерываний */
#define IIR (comport + 2) /* идентификация прерывания */
#define LCR (comport + 3) /* регистр управления регистром */
#define MCR (comport + 4) /* регистр управления модемом */
#define LSR (comport + 5) /* регистр состояния линии */
#define MSR (comport + 6) /* регистр состояния модема */
/* Коды разрешения отдельных прерываний */
#define RDAINT 1
#define THREINT 2
#define RLSINT 4
#define MSINT 8
/* Значение регистра управления модемом */
#define MCRALL 15 /* (DTR, RTS, OUT1 и OUT2 = 1) */
- 8-24 -
#define MCROFF 0 /* все сброшено */
/* Значение регистра разрешения прерывания для его
* включения/выключения
*/
#define IERALL (RDAINT+THREINT+RLSINT+MSINT)
#define IEROFF 0
/* Несколько масок для сброса прерываний */
#define THEREOFF 0xfd
/* Номера идентификатора прерываний */