




                 Глава 8. ПРОГРАММИРОВАНИЕ ПОСЛЕДОВАТЕЛЬНОГО ПОРТА

                 Основы асинхронной последовательной связи
                 Последовательный порт с точки зрения программиста
                 Использование  средств  MS-DOS для программирования
                 последовательного порта
                 Пример программы
                 Заключение


             Последовательный порт в системе MS-DOS обеспечивает  вход  во
         внешний  мир.  Основной  задачей последовательного порта является
         направление и получение данных по шине в виде  потока  битов.  (В
         противоположность параллельному порту,  в котором внутренний байт
         передается целиком). Вы можете использовать последовательный порт
         для  подключения к системе "мыши",  направления данных на принтер
         или для установления автоматической телефонной связи с  использо-
         ванием модема. Хотя системы MS-DOS не нуждаются для работы в пос-
         ледовательном порте,  эти порты стали стандартной периферией сис-
         темы.
             Последовательный порт в системах MS-DOS способен поддерживать
         стандарт асинхронной передача данных RS-232C.  Хотя даже посредс-
         твом ROM-BIOS, стандартной части всех систем MS-DOS, MS -DOS сама
         по  себе  включает  некоторую  поддержку  программирования портов
         RS-232C (например,  прерывание номер 14h), эта поддержка не отве-
         чает требованиям высокоскоростной связи.  Если Вы хотите включить
         в свою прикладную программу  эффективные  возможности  последова-
         тельной связи,  Вы должны осуществлять доступ к последовательному
         порту на аппаратном уровне.  В этой главе показано, как это дела-
         ется.


                     Основы асинхронной последовательной связи

             Говоря о передаче данных,  мы интересуемся  передачей  байтов
         данных от одного устройства к другому, например, от персонального
         компьютера к модему или к  последовательному  принтеру.  Если  мы
         имеем  восемь линий между двумя устройствами,  то мы можем назна-
         чить каждой линии бит и послать сразу один байт данных. Это будет
         параллельная  передача.  Таким образом работает параллельный порт
         персонального компьютера, кроме того, в дополнение к восьми лини-
         ям  данных имеются другие сигнальные линии,  оказывающие помощь в
         передаче данных.
             С другой стороны,  если мы имеем одну линию для передачи сиг-
         налов, то необходимо посылать каждый байт данных последовательно,
         по одному биту.  Более того,  мы может посылать данные синхронно,
         таким образом,  что каждый байт посылается в  ранее  определенное
         время (скажем, один байт каждые х секунд), или асинхронно со ско-
         ростью, которую предварительно определять необязательно.
             Последовательная связь  дешевле,  чем  параллельная,  так как
         требует меньше линий передачи данных - минимум две для двусторон-
         ней связи. Кроме того режим асинхронной передачи оказывает значи-
         тельно меньшее воздействие на аппаратуру ввиду того,  что не тре-
         буется  дополнительное  специальное  оборудование  для  поддержки

                                      - 8-2 -
         синхронизации между передатчиком и приемником.
             Таким образом,  асинхронная  последовательная  связь является
         предпочтительным решением ввиду низкой стоимости и  простоты  ис-
         пользуемых аппаратных средств. Конечно, в этом режиме передачи мы
         должны преобразовывать каждый байт данных в серию битов и  указы-
         вать приемнику начало и конец каждого байта.  На рисунке 8-1 про-
         иллюстрирована концепция асинхронной последовательной связи.
             Предположим, что мы умеем преобразовывать каждый байт в поток
         единиц и нулей,  то есть биты,  которые могут быть переданы через
         среду связи (например,  телефонную линию).  В самом деле, универ-
         сальный асинхронный приемопередатчик (UART), как мы увидим в сле-
         дующем разделе,  выполняет точно такую же функцию.  Обычно,  в то
         время как линия находится в режиме ожидания, для демонстрации то-
         го, что линия в порядке, по ней передается единица, обозначая не-
         занятость линии. С другой стороны, когда линия находится в состо-

    ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─  ┐
                                    Восстанавливается один байт
    │                               └  7  6  5  4  3  2  2  1 ┘└────┐
      ┌───Принимается один символ────┐└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘ ┌───┐│
    │  │A││B││7││6││5││4││3││1││0││C│                      ││
   --> └─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘ ┌─────┬─────────┬─────┐  │ ┌─┘└─┐
   ┌──────────────────────────────────┤  D  │         │     │    │    │
   ││    от модема                    └─────┴─────────┴─────┘  │ ├────┤
   │  UART на приемном конце             скорость в бодах        │    │
   │└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │    │
   │                                                             ├────┤
   │  ┌───────┐                                                  │    │
   └──┤       ├──┐                                               └────┘
      └───────┘  │                                           Принимающий
                 │        Телефонная линия                    компьютер
                ┌┼┐___________________________┌─┐
                └─┘      <--                  └┼┘
                                               │
                                               │
                                               │
                                               │       ┌───────┐
                                               └───────┤ модем ├────────┐
         ┌────────┐                                    └───────┘        │
         │        │                                                     │
         │   PC   │                                                     │
      ┌──┴────────┴──┐                                                  │
      │              │                                                  │
      └┐┌────────────┘                                                  │
       ││  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─  ┐ │
      ││  │ ┌──────── Байт───────────┐                                 │
       ││      7  6  5  4  3  2  2  1                                 │ │
       │└──┘  └─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘┌───Посылаемый  один символ────┐  │
       └───┐                   │A││B││7││6││5││4││3││1││0││C│ │ │
              ┌─────┬─────────┬─────┐  └─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘   │
           │  │     │         │  D  ├───────────────────────────────────┘
              └─────┴─────────┴─────┘         к модему -->            │
           │  скорость в бодах     UART в последовательном адаптере PC
           └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

                   Рис.8-1. Асинхронная последовательная связь.
                 A-стоп; B-четность; C-начало; D-сдвиговый регистр

                                      - 8-3 -
         янии   логического  нуля,  говорится,  что  она  стоит  в  режиме
         выдерживания интервалов. Таким образом, логические единица и ноль
         рассматриваются, соответственно, как MARK и SPACE.
             В асинхронной  связи изменение условия состояния линии с MARK
         на SPACE означает начало символа (смотри рисунок 8-2).  Это назы-
         вается стартовым битом. За стартовым битом следует комбинация би-
         тов,  представляющая символ, и затем бит контроля четности. Нако-
         нец,   линия   переходит   в  состояние  ожидания  MARK,  которая
         представляет собой стоповый бит и означает конец текущего  симво-
         ла.  Число битов, используемых для представления символа, называ-
         ется длиной слова и обычно бывает равно семи  или  восьми.  Конт-
         рольный  бит используется для выполнения элементарной проверки на
         наличие ошибки.

                  <---------     Направление передачи
      Линия                                     Линия возвращается в
      свободна                                   свободное состояние
             A                                          
      B ────┐ ┌ ─ ┬ ─ ┬ ─ ┬ ─ ┬ ─ ┬ ─ ┬ ─ ┬ ─ ┬ ─ ┬───┬───────┐  ┌ ─ ─
            │  │ 0   1   2   3   4   5   6   7     │           │
      C ─ ─ └──┘ ─ ┴ ─ ┴ ─ ┴ ─ ┴ ─ ┴ ─ ┴ ─ ┴ ─ ┴ ─ ┴ ─ ┴ ─ ─ ─ └──┘ ─ ─
              └──────── 7 или 8 бит данных ───┘               
         Стартовый                       бит четности│    Начало другого
           бит                                       │      символа
                                              бит стоповый
                      Время ----->

              Рис.8-2. Представление в асинхронной  последовательной
                         связи формата одиночного символа
                A-длительность 1 бита; B-MARK или 1; C-SPACE или 0

             Как передатчик  (или  приемник) узнают о длительности каждого
         бита?  Действительно,  и передатчик,  и приемник должны знать его
         длительность или детектирование битов будет невозможно.  Длитель-
         ность каждого бита определяется генераторами  тактовых  импульсов
         приемника и передатчика. Отметим, однако, что генераторы в прием-
         нике и передатчике должны иметь одну и ту же частоту,  но не тре-
         буется, чтобы они были синхронизированы. Выбор частоты генератора
         зависит от скорости передачи в бодах,  которая означает число из-
         менений состояния линии каждую секунду. Номинально  тактовая час-
         тота "16-кратная скорость передачи в бодах" означает,  что  линия
         проверяется  достаточно часто для надежного распознавания старто-
         вого бита.
             Существует одно  обычное состояние линии,  которое иногда ис-
         пользуется для привлечения внимания приемника. Нормальным состоя-
         нием  линии  является  MARK (или 1) и начало символа определяется
         переходом SPACE (0). Если линия находится в состоянии SPACE в те-
         чение периода времени большем,  чем время,  которое она затратила
         бы на получение всех битов символа,  тогда мы говорим, что насту-
         пило  состояние  BREAK.  В  кодах ASCII отсутствует представление
         BREAK - это означает,  что линия  "умерла"  на  непродолжительный
         промежуток времени, который составляет BREAK.



                                      - 8-4 -
                     Контроль по четности и обнаружение ошибок

             Ранее мы упоминали, что бит контроля четности полезен для об-
         наружения  ошибок.  Например,  если выбрана проверка на четность,
         этот бит устанавливается таким образом,  что общее число единиц в
         текущем  слове  является четным (такая же логика используется для
         проверки на нечетность).  В приемнике четность вычисляется заново
         и сравнивается с битом контроля четности.  Если они не равны,  то
         приемник сообщает,  что имеет место ошибка четности.  Главный не-
         достаток обнаружения ошибки посредством проверки на четность зак-
         лючается в том,  что можно только обнаружить ошибки, которые вли-
         яют на один единственный бит.  Например,  битовая комбинация 0100
         0001 0 (ASCII A),  переданная восемью битами с проверкой на  чет-
         ность, может измениться (скажем,из-за шума в линии) на 0100 01110
         (ASCII G), однако приемник  не  обнаружит ошибку, так как провер-
         ка на четность выполняется.

                     Связь с использованием стандарта RS-232C.

             Ранее мы  упоминали  о  передаче по телефонной линии единиц и
         нулей.   Несмотря на то,  что в персональном компьютере мы  пред-
         ставляем единицы и нули посредством уровней напряжения,  сигналы,
         передаваемые по телефонной линии обычно являются тонами различной
         частоты.  Устройство,  которое находится между аппаратурой персо-
         нального компьютера и передающей линией и делает возможной  пере-
         дачу данных,   называется модемом (модулятор/демодулятор).  Модем
         может преобразовывать информацию в  представление "напряжение/нет
         напряжения" цифровых схем и обратно,  а так же аналоговые сигналы
         (например,  тоны), предназначенные для передачи по телефонной ли-
         нии.  Стандарты, такие как RS-232C (выдвинутый Ассоциацией элект-
         ронной промышленности,  EIA),  описывают метод обмена информацией
         между  модемом  (или,  в  соответствии с терминологией ассоциации
         EIA,  "аппаратура передачи данных,  DCE") и  связной  аппаратурой
         персонального  компьютера  (или  "оконечная  аппаратура обработки
         данных,  DTE"). Модем может работать в двух режимах: полудуплекс-
         ном  и  дуплексном.  В полудуплексном режиме модем может осущест-
         влять передачу только в одном направлении в один промежуток  вре-
         мени,   в   то   время   как   при  работе  в  дуплексном  режиме
         осуществляется независимая двухсторонняя связь.  Стандарт RS-232C
         обеспечивает управление такими сигналами, как "запрос-на-передачу
         (RTS)" и "открыт-для -передачи (CTS)", которые могут быть исполь-
         зованы  для координации процесса передачи и приема данных. Термин
         "квитирование установления связи" используется для описания коор-
         динации приема и передачи сигналов.  Как показано на рисунке 8-3,
         стандарт RS-232C соответствует кабелю и соединителям,  используе-
         мым для связи персонального компьютера и модема.
             Несмотря  на то, что мы использовали модем в качестве примера
         аппаратуры передачи данных (DCE),  другие устройства,  такие  как
         "мышь" или принтер с соответствующей схемой,  также могут обмени-
         ваться данными с персональным компьютером  через последовательный
         порт.  Таким образом, в этой главе все упоминания о модеме прием-
         лемы в равной степени как к последовательному принтеру  так  и  к
         последовательной "мыши".

                                      - 8-5 -
              ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
               ОАОД   ┌─────   Номер контакта  ────────┐   DCE
              │  ┌─────┐                             ┌─────┐  │
                 │    2├─────────────ПД──────────────┤ 2   │
              │  │    3├──────────────П──────────────┤ 3   │  │
                 │    4├─────────────ЗП──────────────┤ 4   │
              │  │    5├────────────ОДП──────────────┤ 5   │  │
                 │    6├────────────ГПД──────────────┤ 6   │         к
              │  │ ┌──7├───────────Общий─────────────┤ 7──┐│  │ телефонной
                 │   8├───────────ДСПЛ──────────────┤ 8  │       линии
              │  │   20├────────────ТДГ──────────────┤20   │  │        
                 │   22├─────────────RI──────────────┤22   │           │
              │  │    .│                             │ .   │  ├────┐   │
                 │    .│                             │ .   │       │   │
            ┌─┤  │    .│                             │ .   │  │    │   │
    RS ---> │    └─────┘                             └─────┘       │   │
            │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘    │   │
        ┌───┴────┐                                                 │   │
        │        │                                                 │   │
        │        │                                                 │   │
     ┌──┴────────┴──┐                                            ┌─┴───┴─┐
     │              │                                            │       │
     └──────────────┘                                            └───────┘
        PC или ОАОД                                          Модем или АПД
                              Рис.8-3. Связь RS-232C.
         ОАОД - оконечная аппаратура обработки данных; RS - кабель RS-232C;
         ПД - передача данных; П - прием данных; ЗП - запрос на передачу;
         ОДП - открыт для передачи; ГПД - готов к передаче данных;
         ДСПЛ - детектор сигнала приемной линии; ТДГ - терминал данных
         готов;  АПД - или аппаратура передачи данных


                       Управление потоком с помощью XON/XOFF

             В дополнение к квитированию  установления  связи  посредством
         аппаратных сигналов RTS/CTS,  для достижения управления потоком с
         использованием программного обеспечения  применяются  специальные
         управляющие  символы  ASCII  (Control-Q/Control-S или XON/ XOFF).
         Управлять потоком необходимо ввиду того, что иногда либо передат-
         чик  либо  приемник не могут поддерживать скорость передачи и они
         должны иметь возможность информировать другую сторону о необходи-
         мости  остановки  на время,  требуемое для того,  чтобы отставшая
         сторона смогла догнать другую.
             Предположим, что приемник имеет буфер для хранения  поступаю-
         щих символов. Как только буфер после заполнения закрывается, при-
         емник может послать символ XOFF  передатчику,  сигнализируя,  что
         передача должна быть приостановлена. Конечно, приемник должен по-
         нять значение XOFF и прекратить передачу символов.  Затем,  когда
         приемник обработает символы (скажем,  запишет их в файл на диске)
         и буфер освободится,  тогда посылается символ XON,  показывающий,
         что передача может быть продолжена.  Эта схема управления потоком
         широко применяется ввиду ее простоты.  Большинство связных  прог-
         рамм предоставляют возможность дуплексной связи с управлением по-
         током, основанном на применении символов XON/XOFF.



                                      - 8-6 -
                 Последовательный порт с точки зрения программиста

             Аппаратура последовательного порта в системах MS-DOS известна
         как последовательный адаптер или асинхронный связной адаптер (да-
         лее  мы  будем называть его последовательным адаптером).  Адаптер
         основан на Intel 8259 UART (универсальный асинхронный приемопере-
         датчик), имеет порт RS-232C для подключения к модему и, как адап-
         тер дисплея,  программируется посредством набора регистров.  Мик-
         ропроцессор  имеет  доступ  к  регистрам через ранее определенные
         адреса порта ввода/вывода.
             Универсальный асинхронный  приемопередатчик Intel 8250 управ-
         ляется посредством записи в набор восьмибитовых регистров и  чте-
         ния из них.  Эти регистры доступны программисту через адреса пор-
         та.  Адреса портов задаются последовательно,  поэтому  достаточно
         знать  адрес  первого порта.  Он также известен как базовый адрес
         последовательного адаптера. В персональном компьютере IBM PC двум
         последовательным портам COM1 и COM2 присвоены базовые адреса пор-
         та 3F8h и 2F8h соответственно. Так, для последовательного адапте-
         ра COM1 первый регистр имеет адрес 3F8h, следующий 3F9h и так да-
         лее.
             В 8250  имеется семь физических регистров и они описываются в
         порядке возрастания начального номера, начиная с базового адреса.
         Как показано на  рисунке 8-4,  базовый адрес порта имеет один ре-
         гистр,  который делится на два,  как приемный буферный регистр  и
         регистр хранения передачи (THR),  который используется для хране-
         ния одного передаваемого или принимаемого символа.  Затем следует
         регистр разрешения прерываний,  который используется для разреше-
         ния или блокировки генерации прерываний последовательным  адапте-
         ром.  Третий регистр, называемый регистром идентификации прерыва-
         ний,    содержит    сообщение     универсального     асинхронного
         приемопередатчика  об идентичности прерывания.  Затем следует ре-
         гистр управления линией,  используемый для установления различных
         связных  параметров,  таких как длина слова,  количество стоповых
         битов,  четность и скорость передачи в бодах. Пятый регистр - это
         регистр управления модемом, который используется для передачи мо-
         дему сигналов,  таких как DTR (терминал готов) и RTS  (запрос  на
         передачу). Наконец, два последних регистра, регистр состояния ли-
         нии и регистр состояния модема, показывают соответственно состоя-
         ние линии и модема.
             Первые два регистра применяются также для установки  скорости
         передачи  в  бодах.  Скорость  передачи  в бодах определяется как
         16-битовый делитель тактовой частоты, используемой для последова-
         тельного адаптера (1.8432 МГц в большинстве систем MS -DOS). Зна-
         чение делителя вычисляется по формуле

                                 1,843,200
             делитель= ------------------------------
                       16 Х скорость передачи в бодах

             Чтобы установить скорость передачи в бодах,  Вы должны проде-
         лать следующее:
             1. Установить в 1 наиболее значимый бит  регистра  управления
                линией  (он  называется битом защелки доступа делителя или
                DLAB).
             2. Загрузить младший и старший байты делителя соответственно в
                приемный буфер и регистр разрешения прерываний.
             3. Установить DLAB в 0 для обеспечения нормальной работы уни-
                версального асинхронного приемопередатчика.


                                      - 8-7 -
          A     Приемный буфер/регистр хранения передачи
          B     ┌───┬───┬───┬───┬───┬───┬───┬───┐
       (COM1-3F8│ 1.│   │   │   │   │   │   │ 2.│
       COM2-2F8)└───┴───┴───┴───┴───┴───┴───┴───┘

                Регистр разрешения прерывания
          B+1   ┌───┬───┬───┬───┬───┬───┬───┬───┐
                │ 0 │ 0 │ 0 │ 0 │ 3.│ 4.│ 5.│ 6.│
                └───┴───┴───┴───┴───┴───┴───┴───┘
                Установить бит в 1 для разрешения

                Регистр идентификации прерывания
          B+2   ┌───┬───┬───┬───┬───┬───┬───┬───┐
                │ 0 │ 0 │ 0 │ 0 │ 0 │   │   │ 7.│
                └───┴───┴───┴───┴───┴───┴───┴───┘
                                    └──3-битовый идентификатор прерывания
                                     110 = состояние линии
                                     100 = приемные данные
                                     010 = буфер передачи свободен
                                     000 = состояние модема
                Регистр управления линией
          B+3   ┌───┬───┬───┬───┬───┬───┬───┬───┐
                │ 8.│   │    9.     │10.│  11.  │
                └───┴───┴───┴───┴───┴───┴───┴───┘
                      └─BREAK: 1 устанавливает линию в SPACE
                Регистр управления модемом
          B+4   ┌───┬───┬───┬───┬───┬───┬───┬───┐  a. - OUT2
                │ 0 │ 0 │ 0 │12.│ a.│ b.│ c.│ d.│  b. - OUT1
                └───┴───┴───┴───┴───┴───┴───┴───┘  c. - RTS
                                  └── 13.          d. - DTR
                Регистр состояния линии
          B+5   ┌───┬───┬───┬───┬───┬───┬───┬───┐
                │ 0 │14.│15.│16.│17.│18.│19.│20.│
                └───┴───┴───┴───┴───┴───┴───┴───┘

                Регистр состояния модема           a. - RLSD
          B+6   ┌───┬───┬───┬───┬───┬───┬───┬───┐  b. - Delta RLST
                │ a.│ RI│DSR│CTS│ b.│ c.│ d.│ e.│  c. - Delta RI
                └───┴───┴───┴───┴───┴───┴───┴───┘  d. - Delta DSR
             Рис.8-4. Регистры в 8250 UART.        e. - Delta CTS

             A - адрес порта или регистр
             B - базовый адрес; 1. - бит 7 данных;  2. - бит 0 данных;
             3. - состояние модема; 4. - состояние линии приема;
         5. - регистр хранения передачи свободен; 6. - прием данных
         разрешен; 7. - 0 означает, что прерывание ждет;
         8. - бит доступа к защелке; 9. - четность:  000=нет,
         001=нечетность, 011=четность; 10. - число стоповых битов;
         11. - длина слова: 10-7, 11-8; 12. - проверка обратного цикла;
         13. - должен быть 1 для  прерывания ввода/вывода персонального
         компьютера; 14. - передача свободна; 15. - регистр хранения пере-
         дачи свободен; 16. - обнаружен BREAK; 17. - ошибка кадровой
         синхронизации; 18. - ошибка четности;  19. - ошибка выхода за
         границы; 20. - данные для приема готовы;

             Применяя этот подход Вы можете установить любое значение ско-
         рости передачи в бодах.  Обратите внимание, что максимально  воз-

                                      - 8-8 -
         можной скоростью передачи является  1/16  тактовой  частоты,  или
         115,200  бод (для этой скорости передачи делитель равен 1).  Этот
         предел вытекает из того, что делитель не может быть меньше едини-
         цы.  Для  установки скорости передачи в бодах Вы можете также ис-
         пользовать прерывание BIOS 14h. Мы рассмотрим применение BIOS да-
         лее в этой главе.

               Управляемый прерываниями последовательный ввод/вывод

             Существует два общих метода ввода/вывода в любой вычислитель-
         ной системе:  упорядоченный и управляемый прерываниями.  Упорядо-
         ченность относится к повторяющейся  проверке  состояния  регистра
         устройства ввода/вывода для инициализации требуемой транзакции. В
         упорядоченном вводе/выводе программа, запрашивающая символ ввода,
         многократно считывает состояние регистра в устройстве ввода/выво-
         да до тех пор, пока оно не покажет, что символ доступен для ввода
         (или  до  тех пор,  пока программа не решит,  что "время закончи-
         лось"). Когда состояние указывает, что имеется готовый для работы
         символ, программа считывает его из соответствующего регистра уст-
         ройства ввода/вывода.  Сходная последовательность "ждать,  до тех
         пор пока не готов,  затем писать" используется при выведении сим-
         волов на устройство ввода/ вывода.  Таким образом, дальнейшее вы-
         полнение  программы  приостанавливается  до завершения выполнения
         операции ввода/вывода.
             Большой проблемой  для упорядоченного ввода/вывода через ком-
         муникационный порт является то,  что при скорости  передачи  выше
         300  бод  программе трудно что-либо сделать с получаемым символом
         кроме как отображать его на экране.  Рассмотрим следующий пример.
         Предположим,  что  мы читаем символы со скоростью 300 бод и имеем
         следующие связные параметры:  длина слова 7 бит, проверка на чет-
         ность и один стоповый бит, который вместе со стартовым битом, до-
         бавляет до 10 бит на символ. Вы ожидаете получать около 30 симво-
         лов  каждую секунду. После чтения  символа  программа имеет около
         1/30 секунды для выполнения других операций. Если  Вы не  желаете
         потерять какие-либо символы,то в это время Вы должны снова начать
         упорядочение порта.  Что произойдет,  когда скорость возрастет до
         9600 бод?  Временной интервал между символами слишком мал для вы-
         ведения символа на экран дисплея,  не позволяет  интерпретировать
         специальные символы и эмулировать терминал.
             В подходе,  основанном на управлении прерываниями,  программа
         предоставляет  возможность  прерываниям  устройства  ввода/вывода
         поступать непосредственно на центральный процессор,  который про-
         должает выполнять свою работу, не связываясь с устройством. Когда
         устройство готово к вводу/выводу, оно сигнализирует об этом цент-
         ральному процессору посредством аппаратуры.  Получив этот сигнал,
         центральный процессор сохраняет свое текущее состояние и вызывает
         подпрограмму  обслуживания  прерываний,  адрес которой хранится в
         таблице векторов прерываний.  Эта подпрограмма выполняет операцию
         ввода/вывода,  затем восстанавливает состояние машины и возвраща-
         ется в прерванную программу.  Учитывайте регистр символов, посту-
         пающих в коммуникационный порт персонального компьютера.  Органи-
         зовав где-нибудь  некоторые  ячейки  памяти  (буфер),  Вы  можете
         использовать  простую подпрограмму обработки прерываний,  которая
         быстро считывает символ из коммуникационного  порта  и  сохраняет
         его в следующей доступной ячейке памяти в буфере.  Символы не бу-
         дут утеряны в процессе считывания и сохранения  символа драйвером
         прерываний  перед поступлением следующего символа.  Эта несложная

                                      - 8-9 -
         задача достаточно проста для выполнения в короткие временные  ин-
         тервалы   между   поступающими  символами  при  скорости передачи
         9600 бод.  Прелесть этого метода заключается в том, что время об-
         работки главной программой символов, хранящихся в буфере, не име-
         ет значения. Конечно, существует риск переполнения буфера, но эта
         проблема  может быть решена простым увеличением его размера. Если
         этот способ не очень хорош,  то для избежания переполнения буфера
         можно использовать управление потоком с помощью XON/XOFF.
             Из наших рассуждений должно стать очевидным,  что управляемая
         прерываниями буферная связь с использованием управления потоком с
         помощью XON/XOFF, предпочтительнее упорядоченной связи.

                       Прерывания последовательного адаптера

             Последовательный адаптер  персонального компьютера может быть
         запрограммирован для прерывания  работы  центрального  процессора
         всякий  раз как только происходит одно из четырех событий (смотри
         рисунок 8-5). Универсальный асинхронный приемопередатчик присваи-
         вает приоритет каждому из четырех событий. В таблице 8-1 перечис-
         лены четыре прерывания.
                                                         Таблица 8-1
                       Прерывания последовательного адаптера
         ───────────────┬────────────────────────────────────────────────
             Приоритет  │  Идентификатор прерывания
         ───────────────┼────────────────────────────────────────────────
                1       │  Состояние приемной линии (RLS)
                2       │  Доступность данных для приема (RDA)
                3       │  Регистр хранения передачи свободен (THRE)
                4       │  Состояние модема (MS)
         ───────────────┴────────────────────────────────────────────────

               Символы ASCII
             ┌─┬─┬─┬─┬─┬─┬─┬─┐        ┌─────────────────────────────────┐
             └─┴─┴─┴─┴─┴─┴─┴─┘        │  Регистр разрешения    1 2 3 4  │
                                      │         прерывания    ┌─┬─┬─┬─┐ │
                                      │                       └─┴─┴─┴─┘ │
                                      │                           │     │
                                      │                                │
                                      │  Идентификация        ┌─┬─┬─┬─┐ │
                                      │    прерывания         └─┴─┴─┴─┘ │
                     Последовательный │                 ┌────────┘      │
                          адаптер     └──────────────┐                 │
                                                     │ ││││││││││││││││ │
                                                     └─┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴─┘
                                                      
                                                     ┌─┐
                           Прерывание к 8259A       ─┘ └──

                  Рис.8-5. Прерывания последовательного адаптера

             Событием высшего приоритета  является  прерывание  "состояние
         приемной  линии"  (RLS),  которое обрабатывается чтением регистра
         состояния линии. Прерывание RLS имеет место, когда происходит од-
         но из следующих событий:

             - Линия  отключается  (логический 0) на период времени больше
               требуемого для получения символа.

                                      - 8-10 -
             - Символ  получен до того,  как был считан предыдущий (ошибка
               выхода за границы).
             - Ошибка при проверке на четность.
             - При восстановлении символа из полученных битов не обнаружен
               стоповый бит (ошибка кадровой синхронизации).

             Затем следует  прерывание  "доступность  данных  для  приема"
         (RDA),  которое возникает при готовности символа  для  чтения  из
         приемного буферного регистра.  Оно может быть сброшено путем счи-
         тывания символа из регистра.
             Следующий приоритет  имеет прерывание "регистр хранения пере-
         дачи свободен" (THRE). Как следует из имени прерывания, оно имеет
         место,  когда регистр, хранящий символ, предназначенный для пере-
         дачи (имеет тот же адрес порта,  что и регистр приемного буфера),
         свободен.  Прерывание  обрабатывается  записью в этот регистр или
         чтением из регистра идентификации  прерывания.  Необходим  другой
         метод сброса этого прерывания, так как иногда даже через прерыва-
         ния универсального асинхронного приемопередатчика сообщается, что
         буфер передачи пуст и, возможно, отсутствует информация для пере-
         дачи.
             Самый низкий  приоритет  имеет  прерывание "состояние модема"
         (MS). Оно имеет место, когда модем:

             - Получает (посылает) сигнал "открыт для передачи" (CTS).
             - Показывает  свою  готовность установкой линии "набор данных
               готов" (DSR).
             - Получает  сигнал,  устанавливающий линию "индикатор кольца"
               (RI) в логическую 1.
             - Определяет  сигнал  переноса (тот тон,  который Вы слышите,
               когда вызываете номер и модем отвечает), устанавливая линию
               "определение сигнала приемной линии" (RLSD) в 1.

         Прерывание состояния  модема может быть сброшено чтением регистра
         состояния модема.
             Эти прерывания  могут  включаться и выключаться индивидуально
         установкой соответствующих битов в регистре  разрешения  прерыва-
         ний. В последовательно-параллельном адаптере фирмы IBM (также как
         и в асинхронном адаптере фирмы IBM) бит с именем OUT2  в регистре
         управления модемом должен быть установлен в 1 до того, как преры-
         вания универсального асинхронного приемопередатчика могут достичь
         центральный процессор.  Когда имеют место прерывания,  последова-
         тельный адаптер собирает их согласно приоритету и направляет пре-
         рывание  высшего  приоритета  в регистр идентификации прерывания.
         Адаптер прекращает отвечать на последующие прерывания равного или
         меньшего приоритета до тех пор, пока не определит, что текущее не
         обслужено подпрограммой обслуживания  прерываний. Программируемый
         контроллер прерываний 8259A.
             В  системах  MS-DOS  центральный  процессор   (микропроцессор
         80х86) непосредственно не принимает  прерывания,  поступающие  от
         аппаратных средств,  таких как последовательный адаптер. Прерыва-
         ния аппаратуры сначала обслуживаются чипом программируемого конт-
         роллера  прерываний  Intel 8259A.  8259A действует как "приемщик"
         центрального процессора.  Контроллер 8259A,  как  программируемое
         устройство, принимает до восьми различных прерываний и может мас-
         кировать (игнорировать) прерывания индивидуально.  8259A отвечает
         на  каждое незамаскированное или разрешенное прерывание и направ-
         ляет его центральному процессору при условии,  что никакое другое

                                      - 8-11 -
         прерывание высшего приоритета не обслуживается в настоящее время.
             Как контроллер 8259A присваивает приоритеты?  Как  и  универ-
         сальный асинхронный приемопередатчик имеет свой метод определения
         приоритетов прерываний последовательного адаптера, 8259A обладает
         своей схемой присваивания приоритетов прерываниям. Последователь-
         ный адаптер является только одним из аппаратных  средств, которые
         могут направлять прерывания контроллеру 8259A.  Каждое устройство
         жестко смонтировано или соединено проводниками с различными вход-
         ными устройствами, известными как входные устройства запроса пре-
         рываний (IRQ) контроллера 8259A.  Поэтому, обычно говорят об IRQ,
         присвоенном аппаратному прерыванию.  Другой характеристикой, свя-
         занной с IRQ прерывания,  является номер прерывания, используемый
         для обращения к отдельному прерыванию.  В персональном компьютере
         IBM PC этот номер равен восьми плюс IRQ. Когда имеет место преры-
         вание,  центральный процессор использует его номер в качестве ин-
         декса в таблице,  известной как таблица векторов прерываний (рас-
         положена  в  начале памяти),  которая содержит адрес подпрограммы
         обработки данного прерывания.  Так как контроллер 8259A связывает
         высшие приоритеты с низкими IRQ, аппаратные устройства, требующие
         максимального внимания,  имеют низкие IRQ. Таким образом, систем-
         ный таймер имеет IRQ0, клавиатура имеет IRQ1 и так далее.
             Несмотря  на то,  что MS-DOS 3.3 поддерживает четыре коммуни-
         кационных порта, с COM1 по COM4, эта поддержка не означает ничего
         кроме обладания четырьмя драйверами с этими  именами,  каждый  из
         которых  поддерживается  безбуферно  и  только упорядоченным вво-
         дом/выводом.  Так как мы  интересуемся  управляемыми прерываниями
         последовательным вводом/выводом, детали поддержки системой MS-DOS
         коммуникационных портов не относятся к данному вопросу.
             В персональном  компьютере  IBM  PC  только два первых порта,
         COM1 и COM2,  имеют определенные номера IRQ и номера  прерываний.
         Другие  последовательные порты,  такие как COM3 и COM4 могут быть
         использованы для управляемого прерываниями ввода/вывода после ус-
         тановки  адаптеров  и  присваивания номеров IRQ посредством соот-
         ветствующей установки перемычек. Как только станет известен номер
         IRQ, программирование портов COM3 и COM4 выполняется таким же об-
         разом,  как и портов COM1 и COM2. Более того, до конца этой главы
         мы будем рассматривать только порты COM1 и COM2.
             Двум последовательным портам COM1 и COM2 присвоены  соответс-
         твенно IRQ4  и IRQ3,  то есть номера прерываний 12 и 11 (десятич-
         ные).  Между прочим,  номера прерываний должны быть известны, так
         как  функциональные вызовы DOS (посредством программного прерыва-
         ния 21h) с функциональными номерами 35h и 25h могут быть  исполь-
         зованы соответственно для получения и установки векторов прерыва-
         ний.
             Есть еще  несколько моментов,  о которых необходимо упомянуть
         перед началом разговора о программировании последовательных  пор-
         тов  для управляемого  прерываниями  ввода/вывода. Микропроцессор
         80х86 автоматически делает невозможными все прерывания в то время,
         когда  он передает управление обслуживающей подпрограмме текущего
         прерывания.  Несмотря на то, что контроллер 8259A во время обслу-
         живания прерывания задерживает последующие прерывания того же или
         меньшего приоритета, прерывания старшего приоритета все еще полу-
         чают подтверждение о приеме, если установлен флаг прерывания. Ес-
         ли мы вновь немедленно не разрешим прерывание до начала  обслужи-
         вания  прерывания  от  последовательного  порта,  многие жизненно
         важные системные функции,  передаваемые прерываниями  (такие  как
         системный таймер,  клавиатура и контроллер диска),  обслуживаться

                                      - 8-12 -
         не будут.  Важно, поэтому, используя команду STI (установить флаг
         прерывания)  переключить прерывание как только обслуживающая под-
         программа примет на себя управление. Это предоставит  возможность
         таймеру,  клавиатуре  и  контроллеру диска прерывать подпрограмму
         обслуживания последовательного  порта,  позволяя  функционировать
         другим устройствам.
             Как мы сможем сообщить контроллеру 8259A о том, что обработка
         последовательного прерывания завершена?  Наша служебная  подпрог-
         рамма должна направить 8259A команду "конец прерывания" (EIO) пе-
         ред возвращением управления центральному процессору.  Несмотря на
         то,  что существуют способы требования EIO для различных IRQ, для
         схемы приоритета,  используемой в персональном компьютере, доста-
         точно направить контроллеру 8259A то,  что известно как "неспеци-
         фический" EOI (код 20h).  Название "неспецифический" вытекает  из
         того,  что эта команда не определяет, какое прерывание обслужива-
         лось. Она просто говорит контроллеру, что обслуживание прерывания
         высшего приоритета завершено. Это разрешает обслуживание прерыва-
         ний того же или высшего IRQ.

                        Программирование контроллера 8259A

             Управляемый прерываниями ввод/вывод требует правильной  уста-
         новки контроллера 8259A.  В противном случае, прерывания, генери-
         руемые последовательным адаптером,  никогда не будут приняты мик-
         ропроцессором  80х86.  Таким образом,  важно выяснить сначала то,
         как мы можем программировать 8259A.
             Как  вся  аппаратура  персонального  компьютера,  контроллер
         8259A программируется посредством двух имен  команд  (регистров).
         Они  расположены в адресах 20h и 21h порта ввода/вывода соответс-
         твенно (рисунок 8-6). Регистр с адресом 21h используется исключи-
         тельно для маскирования прерываний.  Прерывание маскируется (т.е.
         не принимается) в том случае,  если бит,  соответствующий  своему
         IRQ  (считая справа налево,  причем самому правому биту присвоено
         значение IRQ0),  установлен в логическую единицу.  Порт по адресу
         20h  используется  для направления команды прерывания контроллеру
         8259A.  Как мы отметили ранее, в системах MS-DOS это делается за-
         писью 20h в этот порт.
                                      Прерывание к CPU ───┐
                                                          │
         ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┴─ ─ ─ ─ ─ ─ ┐
                        ┌───┬───┬───┬───┬───┬───┬───┬───┐ конец
         │    Порт 20h  │   │   │   │   │   │   │   │   │ прерывания   │
                        └───┴───┴───┴───┴───┴───┴───┴───┘ EOI=20h
         │                             └──── 20h                       │
            8259A                                          IRQ
         │               ┌───┬───┬───┬───┬───┬───┬───┬───┐ 1=a.        │
                         │ 0 │ 0 │ 0 │ 0 │ 3.│ 4.│ 5.│ 6.│ 0=b.
         │               └───┴───┴───┴─┬─┴─┬─┴───┴───┴───┘             │
           Программируемый контроллер прерываний
         └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
                                       │   │         ┐ Последовательные
                                       │   └─── COM2:│ порты
                                       └─────── COM1:│ персонального
                                                     ┘ компьютера
               Рис.8-6. Программируемый контроллер прерываний 8259A

         a. - замаскировать прерывание; b. - разрешить прерывание;

                                      - 8-13 -

             В системах  MS-DOS  первому  последовательному  порту  (COM1)
         присвоен номер IRQ4 (прерывание номер 12),  в то время как второй
         (COM2) имеет IRQ3 (прерывание 11).  Как указывалось ранее,  порты
         COM3 и COM4 могут обрабатываться таким же  образом,  предполагая,
         что  известны номера IRQ,  присвоенные этим портам во время уста-
         новки (только порты COM1 и COM2 имеют заранее  присвоенные номера
         IRQ).  Поэтому,  контроллер 8259A может быть запрограммирован для
         получения прерываний от порта COM1 чтением с порта 21h и обратной
         записью содержания, логически умноженного на EFh. Прерывания пор-
         та COM1 могут быть замаскированы не только повторением предыдущих
         действий, но логическим сложением с 10h, вместо логического умно-
         жения.  Таким образом, контроллер 8259A может быть запрограммиро-
         ван  для  разрешения  прерываний  порта COM1 с помощью следующего
         фрагмента программы

         IN     AL,21H       ;получить текущую  маску прерывания
         AND    AL,EFH       ;разрешить IRQ4
         OUT    21H,AL       ;снова записать его обратно

             Когда прерывания  порта  COM1 снова выключаются,  8259A может
         быть запрограммирован следующим образом

         IN     AL,21H       ;получить текущую маску прерывания
         OR     AL,10H       ;запретить IRQ4
         OUT    21H,AL       ;снова записать его обратно

             Подобное программирование устройств,  выполняемое считыванием
         содержание регистра с последующей записью его обратно с соотвест-
         вующе измененным битом, рекомендуется ввиду того, что мы не нару-
         шаем предварительной установки битов.
             Кроме разрешения  и запрещения приема прерываний,  контроллер
         8259A должен быть проинформирован о завершении обработки обычного
         прерывания.  Как указывалось ранее,  это выполняется направлением
         20h в адрес 20h порта ввода/вывода следующим образом:

         MOV      AL,20H    ;код конца прерывания
         OUT      20H,AL    ;в порт 20Н контроллера 8259A


                 Использование средств MS-DOS для программирования
                              последовательного порта

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

                       Драйвер, TSR или автономная программа

             Существует несколько способов получения доступа к  последова-
         тельному адаптеру в системе MS-DOS.  Вы можете управлять последо-
         вательным портом через устанавливаемый драйвер устройства, выпол-
         няющий  ввод/вывод через этот порт.  В главе 6   "Устанавливаемые

                                      - 8-14 -
         драйверы устройств"   рассматриваются  детали  разработки  такого
         драйвера.  Главным  недостатком такого подхода являются издержки,
         связанные с доступом к драйверу через DOS, а достоинством то, что
         любая  программа,  которая знает о Вашем драйвере,  может его ис-
         пользовать.  Если Вы выбрали этот способ,  то можете обеспечить в
         драйвере возможности IOCTL таким образом,  что связные параметры,
         такие как скорость передачи в бодах и длина слова, могут быть ус-
         тановлены вызовами DOS IOCTL (номер функции DOS 44h).
             Вторым подходом является установка программы TSR (завершенная
         и оставленная резидентно),  которая используется с помощью преры-
         вания BIOS RS-232C (14h) и расширяет свою функциональность  обес-
         печением управляемого прерываниями ввода/вывода. Этот метод также
         дает любой программе доступ к последовательному порту посредством
         Вашего  драйвера  TSR в то время как Вы подтверждаете необходимые
         установки регистра при использовании новых коммуникационных функ-
         ций TSR.  Механизм доступа будет таким же, как вызов функции BIOS
         RS-232C, который мы вскоре опишем.
             Третий метод  заключается  в разработке автономной программы,
         которая включает в себя служебную подпрограмму обработки прерыва-
         ний  последовательного  порта.  В  этом случае,  при запуске этой
         прикладной программы,  Вы можете установить обработчик прерываний
         последовательного  порта  и сбросить его после прекращения работы
         программы. Этот способ создает возможности организации высокоско-
         ростного (9600 бод) последовательного ввода/вывода ввиду наличия
         в нем меньшего количества недостатков по сравнению с двумя други-
         ми методами.
             Независимо  от того,  какой подход  Вы  выберите,  управление
         последовательным портом останется таким же.  Далее мы уделим осо-
         бое внимание деталям.

                   Использование BIOS для последовательной связи

             Вы, вероятно, спросите, можно ли реализовать эффективный ввод
         /вывод через BIOS. К сожалению, нет. BIOS не предоставляет эффек-
         тивной возможности  управления  последовательным  адаптером.  Для
         программирования  последовательного  адаптера  BIOS имеет функцию
         RS-232C, доступную через прерывание 14h. К сожаления, эта функция
         поддерживает  только упорядоченный ввод/вывод,   который не очень
         эффективен ввиду недостатков, изложенных ранее. Тем не менее, эта
         функция идеальна для установки таких параметров коммуникационного
         порта, как скорость передачи в бодах, длина слова и стоповый бит,
         использующий прерывание 14h BIOS.

            Установка коммуникационных параметров с использованием BIOS

             Даже при использовании в последовательном  вводе/выводе  BIOS
         не  столь эффективна как управляемый прерываниями подход. Полезно
         посмотреть,  как коммуникационные параметры (скорость передачи  в
         бодах, длина слова, четность и стоповые биты) могут быть установ-
         лены с использованием функций BIOS RS-232C, доступных по прерыва-
         нию 14h.
             Прерывание 14h с нулем в АН устанавливает параметры  последо-
         вательного порта.  Номер порта должен находиться в DX.  Нуль в DX
         указывает на порт COM1,  в то время,  как  единица  указывает  на
         COM2. Выбранные коммуникационные параметры направляются в регистр
         AL в упакованном формате, показанном на рисунке 8-7. Скорость пе-
         редачи  определяется  3-битовым  значением,  четность - 2-битовым

                                      - 8-15 -
         значением,  число стоповых битов - одним битом и  длина  слова  -
         2-битовым значением. В таблице 8-2 показаны кодированные значения
         каждого коммуникационного параметра. Учтите, что скорость переда-
         чи  драйверов  через  порт  COM в системе DOS 3.3 может достигать
         19200 бод,  в то время,  как ROM-BIOS ограничивается 9600 бодами.
         Для достижения скорости передачи,  не указанной в таблице 8-2, Вы
         можете использовать возможность программирования скорости переда-
         чи  универсального асинхронного приемопередатчика,  рассмотренную
         ранее.
                   Скорость                Длина
                 ┌передачи в ┬Чет-сть┬ a.┬ слова ┐
                 │  бодах    │       │   │       │
                 ┌───┬───┬───┬───┬───┬───┬───┬───┐
                 │   │   │   │   │   │   │   │   │
                 └───┴───┴───┴───┴───┴───┴───┴───┘
                     Боды      Четность   Стоп-биты  Длина слова
                 ┌─────┬─────┐  ┌──┬──┐   ┌──┬──┐    ┌────┬────┐
                 │ 000 │ 110 │  │00│  │   │0 │ 1│    │ 10 │ 7  │
                 ├─────┼─────┤  ├──┤b.│   ├──┼──┤    ├────┼────┤
                 │ 001 │ 150 │  │10│  │   │1 │ 2│    │ 11 │ 8  │
                 ├─────┼─────┤  ├──┼──┤   └──┴──┘    └────┴────┘
                 │ 010 │ 300 │  │01│c.│
                 ├─────┼─────┤  ├──┼──┤
                 │ 011 │ 600 │  │11│d.│
                 ├─────┼─────┤  └──┴──┘
                 │ 100 │1200 │
                 ├─────┼─────┤
                 │ 101 │2400 │
                 ├─────┼─────┤
                 │ 110 │9600 │
                 ├─────┼─────┤
                 │ 111 │     │
                 └─────┴─────┘
           Рис.8-7. Коммуникационные параметры,  упакованные в одиночный
                  байт в формате, требуемом прерыванием 14h BIOS

            a.- стоповые биты; b. - нет; c. - нечетность; d. - четность


                                                         Таблица 8-2
                      Кодированные значения коммуникационных
                          параметров для прерывания 14h
         ──────────────┬──────────────────────┬─────────────────────────
         Имя параметра │ Фактическое значение │ Кодированное  значение
         ──────────────┼──────────────────────┼─────────────────────────
         Скорость пе-  │          110         │           0
         редачи в бодах│          150         │           1
                       │          300         │           2
                       │          600         │           3
                       │         1200         │           4
                       │         2400         │           5
                       │         4800         │           6
                       │         9600         │           7
                       │                      │
         Четность      │           Нет        │        0 или 2
                       │       Нечетность     │           1
                       │        Четность      │           3

                                      - 8-16 -

         Стоповые биты │           1          │           0
                       │           2          │           1
                       │                      │
         Длина слова   │           7          │           2
                       │           8          │           3
         ──────────────┴──────────────────────┴─────────────────────────

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

         pckd_commparams = (baudrate << 5) | (parity <<3) |
                           (stopbits << 2) | (wordlength);

         Мы использовали  операторы сдвига бита и поразрядного логического
         сложения  языка  Си.  Переменные  baudrate,  parity,  stopbits  и
         wordlength  должны быть кодированными значениями коммуникационных
         параметров из последней колонки таблицы 8-2. Как только параметры
         примут такой формат, Вы можете вызывать BIOS прерыванием 14h. Ис-
         пользование функции int86 компилятора Microsoft  С иллюстрируется
         следующим фрагментом программы:

         #include <dos.h>
         #define  BIOS_RS232  0x14
                           /* номер прерывания для обслуживания BIOS */
         static union REGS xr, yr;
             .
             .
             .
         xr.h.ah = 0;        /* номер функции для вызова BIOS RS-232 */
         xr.h.al = pckd_commparams;             /* связные параметры */
         xr.x.dx = port_number;  /* 0 означает COM1, 1 означает COM2 */
         int86(BIOS_RS232, &xr, &yr);               /* сделать вызов */
             .
             .
             .

             Компилятор Microsoft C версии 5.0 облегчает вызов подпрограмм
         BIOS: функция _bios_serialcomm служит интерфейсом между Вашей  Си
         программой и прерыванием BIOS 14h.  Например,  если  Вы  выберете
         8-битовую  длину  слова,  1 стоповый бит,  отсутствие проверки на
         четность и скорость передачи 300 бод, то достаточно вызвать

         _bios_serialcom(_COM_INIT, COM1, (_COM_CHR8 | _COM_STOP1 |
                                           _COM_NOPARITY | _COM_300) );

         Вызов функции _bios_serialcom

         status = _bios_serialcomm(service_code, port_number, data);

         принимает три параметра,  выраженных целым без знака и возвращает
         код  состояния  того  же  типа данных для демонстрации результата
         требуемой операции.  Параметр service_code используется для опре-
         деления  требуемой  операции  и  port_number принимает значение 0
         (COM1) или 1 (COM2).  Значение data зависит от требуемой  услуги.
         Подробности использования этой функции изложены в Microsoft C 5.0

                                      - 8-17 -
         Run-time Library Reference.

                     Получение адреса последовательного порта

             Другим полезным встроенным свойством BIOS является то, что на
         этапе самотестирования при включении (POST) она проверяет наличие
         последовательных адаптеров COM1/COM2 (хотя MS-DOS 3.3 поддержива-
         ет порты COM3 и COM4, BIOS распознает только COM1 и COM2) и, если
         находит тот или другой,  адрес первого регистра каждого  адаптера
         заносится  в  область  памяти,  начиная со смещения нуля сегмента
         14h.  Так как в персональном компьютере 20-битовый физический ад-
         рес равен 10h * 16-битовый сегмент + 16-битовое смещение,  и если
         Ваша система MS-DOS имеет один последовательный порт, назначенный
         как COM1, то тогда слово в физической ячейке 400h будет содержать
         3F8h (если так же присутствует COM2, следующее слово в ячейке 402
         h будет содержать 2F8h).  Таким образом, Вы можете получить адрес
         последовательного адаптера из этой области данных BIOS на  смеще-
         нии 0 и сегменте 40h.  Например,  в Microsoft C Вы можете устано-
         вить базовый адрес порта следующим образом:

         #define BIOS_DATA ((short far *)(0x40000L))
         static short comport,    /* для базового адреса порта */
                port_number;      /* 0 для COM1, 1 для COM2 */
            .
            .
            .
         comport = *(BIOS_DATA + port_number);

         if(comport == 0) /* последовательный адаптер не установлен */
         {
             printf("Последовательный адаптер не установлен!\n";
             exit(1);
         }

             Так как инициализируется переменная comport,  все другие  ре-
         гистры последовательного адаптера могут быть доступны прибавлени-
         ем соответствующих смещений к базовому адресу.  В языке Си Вы мо-
         жете  использовать  директиву препроцессора #define для установки
         адресов этих регистров.  Например, если инициализируется comport,
         Вы  имеете  возможность  обратиться к регистрам последовательного
         порта по их именам, определив их следующим образом:

         #define IER (comport + 1) /* регистр разрешения прерывания */
         #define IIR (comport + 2) /* определение прерывания */
         #define LCR (comport + 3) /* регистр управления линией */
         #define MCR (comport + 4) /* регистр управления модемом */
         #define LSR (comport + 5) /* регистр состояния линии */
         #define MSR (comport + 6) /* регистр состояния модема */


              Настройка на управляемый прерываниями последовательный
                                    ввод/вывод

             После получения  базового адреса порта из области данных BIOS
         Вы должны настроить последовательный порт и установить обработчик
         прерываний перед началом управляемого прерываниями последователь-
         ного ввода/вывода.  Номер прерывания и IRQ прерывания  последова-

                                      - 8-18 -
         тельного порта зависит от того, какой порт используется, COM1 или
         COM2. Как только Вы получили номер прерывания, Вы должны получить
         адрес текущего обработчика и сохранить его.  Таким образом, после
         выхода из программы Вы можете восстановить первоначальное  значе-
         ние вектора прерываний. Функции MS-DOS 35h и 25h, соответственно,
         получают и устанавливают обработчики для заданных номеров  преры-
         ваний.  Для  этой  цели  Microsoft C  предоставляет  подпрограммы
         _dos_getvect и _dos_setvect. Используя язык Си Вы можете проделать
         это следующим образом:

         short int_number;    /* номер прерывания для связного порта */
         void interrupt far s_inthndlr(void);
                              /* установить обработчик */
         static void  (interrupt far *old_handler)()
                              /* место для прежнего */
             .
             .
             .
         /* получить вектор прежнего прерывания и сохранить его */
             old_handler = _dos_getvect(int_number);
         /* установить новый обработчик с именем s_inthndlr
          * запретить прерывания во время замены обработчика
          */
             _disaple();
             _dos_setvect(int_number, s_inthndlr);
             _enable();

             В приведенном примере мы представили обработчик  как  функцию
         типа interrupt,  которая является новым ключевым словом, содержа-
         щимся в Microsoft C 5.0.  В следующем разделе продемонстрировано,
         каким  образом  атрибут interrupt позволяет Вам писать обработчик
         прерываний непосредственно в Microsoft C 5.0 (Turbo C  1.5  имеет
         такую же возможность).
             Следует обратить внимание на использование функций _disable и
         _enable.  Эти две функции соответствуют ассемблерным командам STI
         и CLI.  Таким образом,  мы выключаем прерывания во время перехода
         от  одного  обработчика последовательных прерываний к другому.  С
         другой стороны,  прерывание,  поступающее во время  переключения,
         может привести к тому, что может произойти сбой центрального про-
         цессора, так как вектор прерывания не являлся адресом какого-либо
         действующего обработчика.
             После того,  как обработчик прерываний займет свое место,  Вы
         можете установить коммуникационные параметры и разрешить последо-
         вательному порту генерировать прерывания.  Вы также должны разре-
         шить распознавание этих прерываний контроллером 8259A. Еще раз Вы
         должны запретить прерывания до тех пор,  пока порт и 8259A не бу-
         дут  готовы.  Вот  как  мы  можем  проделать это с использованием
         Microsoft C 5.0.

         /* разрешает маску, зависящую от порта */
         short intmask,  int_enable_mask;
             .
             .
             .
         /* включает прерывания коммуникационного порта.
          * устанавливает 8259A
          */

                                      - 8-19 -
             _disable();
         /* устанавливает регистр управления модемом (порт =  MCR) */
             outp(MCR, MCRALL);
         /* разрешает все прерывания последовательной платы
          * (порт = IER)
          */
             outp(IER, IERALL);
         /* считывает регистр маски прерывания 8259A и записывает его
          * обратно после логического умножения с _int_enable_mask
          */
             intmask = inp(P8259_1) & int_enable_mask;
             outp(P8259_1, intmask);
             _enable();

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


                   Обработка прерываний последовательного порта

             Наш обработчик, s_inthndlr, будет вызван при генерации после-
         довательным портом прерывания.  Мы  должны  немедленно  разрешить
         прием системой последующих прерываний таким образом, чтобы другие
         приоритетные задачи (такие как таймер) могли обрабатываться  мик-
         ропроцессором.
             Следующий шаг - идентификация точной причины, вызвавшей гене-
         рацию прерывания последовательного порта.  Для получения информа-
         ции Вы должны считать содержание регистра идентификации  прерыва-
         ния  (IIR).  Как  только  определится  причина прерывания,  можно
         выполнять его обработку,  как описывалось в разделе,  посвященном
         универсальному асинхронному приемопередатчику.
             Так как последовательный порт способен генерировать  прерыва-
         ние в то время,  пока Вы обрабатываете другое,  необходимо прове-
         рять бит 0 (последний значащий бит) IIR на это условие. Если этот
         бит равен нулю,  то имеется другое прерывание и его следует обра-
         ботать.  С другой стороны, если значение бита равно единице, оче-
         редные прерывания отсутствуют.  В этом случае Вы должны направить
         контроллеру 8259A сигнал "конец прерывания" и выйти из обработчи-
         ка.  Таким  образом,  обработчик  представляет  собой бесконечный
         цикл, который продолжает обрабатывать последовательные прерывания
         до тех пор, пока не прекратится их поступление. В Microsoft C 5.0
         обработчик может быть реализован как

         void interrupt far s_inthndlr(void)
         {
             int c;
             register int int_id, intmask;

         /* прерывания разрешаются немедленно */
             _enable();

             while (TRUE)
             {
         /* чтение регистра идентификации прерываний, IRR */
                  int_id = inp(IRR);
                  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 <stdio.h>
         #include <ctype.h>
         #include <dos.h>
         #include <bios.h>
         #include <conio.h>

         #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

         /* Определения для программируемого контроллера
          * прерываний 8259A
          */
         #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 *0(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

         /* Номера идентификатора прерываний */
         #define MDMSTATUS   0
         #define TXREGEMPTY  2
         #define RXDATAREADY 4
         #define RLINESTATUS 6

         /* Флаги управления потоком XON/XOFF */
         #define XON_RCVD   1
         #define XOFF_RCVD  0
         #define XON_SENT   1
         #define XOFF_SENT  0

         /* Высший и низший коэффициенты для триггера xon-xoff */
         #define HI_TRIGGER(x)  (3*x/4)
         #define LO_TRIGGER(x)  (x/4)

         /* Функция получения нулевого бита целого */
         #define bit0(i)   (i & 0x0001)

         /* Макрокоманда для включения прерывания, "номер разрешения
          * прерывания" которого "i", в противном случае оно запрещает-
          * ся.  Например, прерывание THRE запрещается, если XOFF полу-
          * чен от удаленной системы.
          */

         #define turnon_int(i,j) \
                 if(((j=inp(IER))&i)==0)outp(IER,(j|i))

         #define report_error(s)  fprintf(stderr,s)
         typedef struct QTYPE  /* структура данных для очереди */
         {
                int  count;
                int  front;
                int  rear;
                int  mexsize;
                char *data;
         }   QTYPE;

         static char rxbuf[RXQSIZE], txbuf[TXQSIZE];

         static QTYPE rcvq = {0, }-1, -1, RXQSIZE, rxbuf},
                      trmq = {0, }-1, -1, TXQSIZE, txbuf};

         /* Признаки общего состояния */
         int s_linestatus, s_modemstatus;


                                      - 8-25 -
         static  QTYPE *txq = &trmq, *rxq = &rcvq;
         static  short comport=0,
                       enable_xonxoff = 1,
                       rcvd_xonxoff = XON_RCVD,
                       sent_xonxoff = XON_SENT,
                       send_xon = FALSE,
                       send_xoff = FALSE,
                       int_number = 12;
                       int_enable_mask = 0xef,
                       int_disable_mask = 0x10;

         /* Прототипы функций */
         int s_sendchar(int);
         int s_rcvchar(void);
         int s_setup(short, unsigned);
         int s_cleanup(void);

         char *q_getfrom( QTYPE *, char *);
         int   q_puton( QTYPE *, char *);

         void interrupt far s_inthndlr(void);

         static void  s_rda(void);
         static void  s_trmty(void);
         static void  (interrupt far *old_handler)();
         /*----------------------------------------------------------*/
         main(int argc, char **argv)
         {
             int ch, port_number = 0;

         /* Получить номер порта из командной строки */
             if(argc > 1) port_number = atoi(argv[1]) - 1;
             printf("\nSERIO -- Последовательный ввод/вывод \
         c параметрами 1200,8,N,1 через порт COM%d\n", port_number+1);
             printf("\nСвязь ...\n");

         /* Сначала установить последовательный порт */
             s_setup(port_number, COM_PARAMS);

         /* Последующий бесконечный цикл имитирует терминал.
          * Клавиша Escape служить для общего сброса и возврата.
          */

             while (TRUE)
             {
                  if ((ch = s_rcvchar()) != -1) putch(ch);
                  if ( kbhit() != 0)
                  {
                      ch = getch();
                      if (ch == ESC_KEY)
                      {
                            s_cleanup();
                            return;
                      }

                      else
                          s_sendchar(ch);

                                      - 8-26 -
                  }     /* конец проверки kbhit() */
             }
         }
         /*---------------------------------------------------------*/
         /* s _ i n t h n d l r
          * Обработчик всех прерываний последовательного порта.
          */
         void interrupt far s_inthndlr(void)
         {
             int c;
             registr int int_id, intmask;

         /* Немедленно разрешить прерывания */
             _enable();

             while (TRUE)
             {
         /* Считать регистр идентификации прерывания , IIR */
                int_id = inp(IIR);
                if (bit0(int_id) == 1)
                {
         /* Если бит 0 - 1, то прерывания не ожидаются. Направить прог-
          * раммируемому контроллеру прерываний сигнал  "конец прерыва-
          * ния" и вернуться.
          */
                    outp(P8259_0, END_OF INT);
                    return;
                }
                if (int_id >= RXDATAREADY)
                                turnon_int(THEREINT,intmask);
         /* Обработать прерывание в соответствии с идентификатором.
          * Следующий перечень составлен в соответствии с возрастанием
          * приоритета.
          */


                 switch (int_id)
                 {
                    case MDMSTATUS:    /* считать состояние модема */
                                       s_modemstatus = inp(MSR);
                                       break;
                    case TXREGEMPTY:   s_trmty();
                                       break;
                    case RXDATAREADY:  s_rda();
                                       break;
                    case RLINESTATUS:  /* читать состояние линии */
                                       s_linestatus = inp(LSR);
                                       break;
         /* пропустить, если идентификатор не является одним из
          * перечисленных */
                 }
             }
         }
         /*---------------------------------------------------------*/
         /* s _ r d a
          * Обработать прерывание "доступны данные для приема"
          */

                                      - 8-27 -
         static void s_rda(void)
         {
             registr int intmask;
             char c;
         /* читать из коммуникационного порта */
             c = inp(comport);
             if(enable_xonxoff) {
                if (c == XON_ASCII) {
                      rcvd_xonxoff = XON_RCVD;
         /* Включить прерывание THRE, если оно выключено. */
                      turnon_int(THREINT,intmask);
                      return;
                }
                if(c == XOFF_ASCII) {
                      rcvd_xonxoff = XOFF_RCVD;
         /* Сбросить прерывания THRE */

                      intmask = inp(IER);
                      if (intmask & THREINT0
                                outp(IER, intmask & THREOFF);
                      return;
                 }
             }
             q_puton(rxq, &c);
         /* Проверить, заполнена ли почти очередь (75%) */
             if(enable_xonxoff) {
                if(rxq->count >= HI_TRIGGER(RXQSIZE) &&
                   sent_xonxoff != XOFF_SENT ) {
         /* Установить флаг для направления XOFF */
                   send_xoff = TRUE;
         /* Включить прерывания THRE так, чтобы послать XOFF */
                   turnon_int(THREINT,intmask);
               }
            }
         }
         /*---------------------------------------------------------*/
         /* s _ t r m t y
          * Обработать прерывание "регистр хранения передачи
          * свободен"
          */
         static void s_trmty(void)
         {
             char c;
             registr int ierval;

             if (send_xoff == TRUE) {
                 outp(comport, XOFF_ASCII);
                 send_off = FALSE;
                 sent_xonxoff = XOFF_SENT;
                 return;
             }
             if (send_xon == TRUE) {
                 outp(comport, XON_ASCII);
                 send_xon = FALSE;

                 sent_xonxoff = XON_SENT;
                 return;

                                      - 8-28 -
             }
         /* Поместить символ в регистр хранения передачи */
             if( q_getfrom(txq, &c) != NULL){
                 outp(comport, c);
                 return;
             }
         /* Нечего посылать -- сбросить прерывания THRE */
             ierval = inp(IER);
             if (ierval & THREINT) outp(IER, ierval & THREOFF);
         }
         /*---------------------------------------------------------*/
         /* s _ s e t u p
          * Установить все для связи.
          * Вернуть 1, если установка прошла успешно, в противном
          * случае вернуть 0.
          */
         int s_setup(short port_number, unsigned commparams)
         {
             int intmask;

             if (port_number < 0 || port_number > 1)
                 report_error("Неверный номер порта!\n");

         /* Получить базовый адрес последовательного порта из
          * области данных BIOS */
             comport = *(BIOS_DATA + port_number);
             if (comport == 0)
             {
                 report_error("BIOS не может найти порт!\n");
                 return(0);
             }

         /* Установить маски для программируемого контроллера
          * прерываний 8259A. Для разрешения прерывания порта
          * эта маска логически умножается с маской регистра

          * в 21h. Для запрещения, логически сложить маску
          * запрещения с маской регистра. Номер прерывания
          * определяется как 8 + уровень IRQ прерывания.
          * Коммуникационный порт 1 имеет IRQ 4, порт 2 имеет
          * IRQ 3.
          */
             if (port_number == 0)
             {
                 int_enable_mask = 0xef;
                 int_disable_mask = 0x10;
                 int_number = 12;

             }
             if (port_number == 1)
             {
                 int_enable_mask = 0xf7;
                 int_disable_mask = 8;
                 int_number = 11;
             }

         /* Получить номер старого прерывания и сохранить его. */

                                      - 8-29 -
             old_handler = _dos_getvect(int_number);

         /* Установить новый обработчик с именем s_inthndlr.
          * Запретить прерывания при смене обработчика.
          */
             _disable();
             _dos_setvect(int_number, s_inthndlr);
             _enable();

         /* Установить коммуникационные параметры */
             _bios_serialcom(_COM_INIT, port_number, commparams);

         /* Инициализировать флаги XON/XOFF */
             rcvd_xonxoff = XON_RCVD;
             if (sent_xonxoff == XOFF_SENT)
                 send_xon = TRUE;

             else
                 send_xon = FALSE;
                 send_xoff = FALSE;

         /* Включить прерывания коммуникационного порта и
          * установка 8259A.
          */
             _disable();
         /* Установить регистр управления модемом (порт = MCR) */
             outp(MCR, MCRALL);

         /* Разрешить все прерывания последовательной карты */
             outp(IER, IERALL);

         /* Считать регистр маски прерывания 8259A и записать его
          * обратно после логического умножения с _int_enable_mask.
          */
             intmask = inp(P8259_1) & int_enable_mask;
             outp(P8259_1, intmask);

             _enable();

             return(1);
         }
         /*---------------------------------------------------------*/
         /* s _ c l e a n u p
         /* Очистить после сеанса связи. Сбросить все прерывания. */
         int s_cleanup(void)
         {
             int intmask;

         /* Выключить прерывания последовательной карты */
             _disable();
         /* Первым сбросить регистр разрешения прерывания порта */
             outp(IER, IEOFF);

         /* Сбросить все биты регистра управления модемом */
             outp(MCR, MCROFF);

         /* Затем запретить распознавание контроллером 8259A

                                      - 8-30 -
          * прерываний последовательного порта.
          */
             intmask = inp(P8259 _1) | int_disable_mask;
             outp(P8259_1, inymask);

         /* Восстановить первоначальный вектор прерывания */
             _dos_setvect(int_number, old_handler);

         /* Снова разрешить прерывания */
             _enable();
         }
         /*---------------------------------------------------------*/
         /* s _ s e n d c h a r
          * Поместить символ в очередь на передачу. Вернуть 1, если
          * все в порядке, в противном случае вернуть 0.
          */
         int s_sendchar(int ch)
         {
             int retval, intmask;
             _disable();
             retval = q_puton(txq, (char *)&ch);
             _enable();
         /* Включить прерываниеTHRE в том случае, если оно выключено
          * и не получен XOFF.
          */
             if (rcvd_xonxoff != XOFF_RCVD)
                 turnon_int(THREINT,intmask);
             return(retval);
         }
         /*---------------------------------------------------------*/
         /* s _ r c v c h a r
          * Вернуть символ из очереди на прием.
          * Вернуть 1, если очередь пуста.
          */

         int s_rcvchar(void)
         {
             int ch, intmask;
         /* Если ранее был послан сигнал XOFF, то мы должны направить
          * XON.
          */
             if(enable_xonxoff)
             {
             if(rxq->count <= LO_TRIGGER(RXQSIZE) &&
                  sent_xonxoff != XON_SENT )
                  {
                     send_xon = TRUE;
                     turnon_int(THREINT,intmask);
                  }
             }
             _disable();
             if (q_getfrom(rxq, (chr *)&ch) == NULL)
             {
                 _enable();
                 return(-1);
             }
             else

                                      - 8-31 -
             {
             _enable();
             return(ch);
             }
         }
         /*--------------------------------------------------------*/
         /* q _ g e t f r o m
          * Копировать следующий элемент данных в очередь в
          * определенное местоположение. Также вернуть указатель
          * на этот элемент.
          */
         char *q_getfrom( QTYPE *queue, char *data)
         {
             char *current;
             current = NULL;

             if(queue->front == -1) return(current);
         /* В противном случае искать данные */
             current = &(queue->data[queue->front]);
             *data = *current;
             queue->count--;
             if(queue->count == 0)
             {
         /* Очередь пуста. Сбросить начало, конец и счет */

                queue->front = queue->rear = -1;
                return(current);
             }
         /* Увеличить начальный индекс и проверить на циклический
          * переход.
          */
             if(queue->front == queue->maxsize-1)
                queue->front = 0;
             else
                queue->front++;
             return(current);
         }
         /*--------------------------------------------------------*/
         /* q _ p u t o n
          * Поместить элемент данных в очередь.
          */
         int q_puton(QTYPE *queue, char *data)
         {
         /* Сначала проверить степень заполнения очереди.
          * Если она полна, то вернуть 0.
          */
             if(queue->count == queue->maxsize) return(0);
         /* В противном случае установить на конец и провести
          * проверку на циклический переход.
          */
             if(queue->rear == queue->maxsize-1)
                queue->rear = 0;
             else
                queue->rear++;
         /* Сохранить символ в очереди */
             queue->data[queue->rear] = *data;
             queue->count++;

                                      - 8-32 -
             if(queue->front == -1) queue->front = 0;
             return(1); /* Успешно вставленный элемент */
         }
         _____________________________________________________________


                                Заключение

             В этой  главе  рассмотрены  характеристики аппаратных средств
         последовательного порта в системах MS-DOS и  представлены  методы
         его программирования. Также содержится небольшая коммуникационная
         программа,  написанная на Microsoft C 5.0, для иллюстрации реали-
         зации этих методов на практике. Программирование последовательно-
         го порта для  управляемого  прерывания  ввода/вывода  выполняется
         следующим образом:

             1. Получить  базовый адрес выбранного коммуникационного порта
                из области данных BIOS на сегменте 40h и со смещением 0.
             2. Используя функцию MS-DOS 35h,  получить адрес старой  под-
                программы обслуживания прерывания  для  номера  прерывания,
                соответствующего данному адаптеру, и сохранить его.
             3. Используя функцию MS-DOS 25h, установить для номера преры-
                вания  Вашу собственную подпрограмму обслуживания прерыва-
                ния.
             4. С  использованием функции BIOS 14h установить коммуникаци-
                онные параметры адаптера.
             5. Установить очереди приема и передачи для содержания входя-
                щих и исходящих символов.
             6. Включить в регистре управления модемом необходимые сигналы
                (например,  DTR - терминал готов и RTS - запрос на переда-
                чу).
             7. Разрешить все прерывания адаптера (установкой битов с 0 по
                3 в регистре разрешения прерывания в 1).
             8. Так же включить бит OUT2 в регистре управления модемом для
                разрешения прерываний последовательного адаптера.
             9. Запрограммировать контроллер 8259A для распознавания  пре-
                рываний с IRQ этого адаптера (путем установки соответству-
                ющего бита регистра маски прерывания, доступного через ад-
                рес порта 21h, в 0).

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

             1. Выключить прерывания последовательного адаптера.
             2. Сбросить биты регистра управления модемом.
             3. Восстановить старую подпрограмму обработки прерывания.
             4. Замаскировать прерывания для этого IRQ в 8259A.
