On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

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

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

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

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

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

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

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

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

2.8. Программирование асинхронного адаптера средствами MS-DOS

2.9. Стандартные функции библиотеки Си для работы с последовательным портом

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

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

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

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

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

  • COM1, COM3 - IRQ4 (соответствует INT 0Ch);
  • COM2, COM4 - IRQ3 (соответствует INT 0Bh).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

typedef union _ICR_ {

   struct {

      unsigned char in_ready : 1;
      unsigned char out_ready : 1;
      unsigned char err : 1;
      unsigned char change : 1;
      unsigned char reserv : 4;

   } bit_reg;

   unsigned char byte;

} ICR;

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

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

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

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

В файле uart_reg.h данный регистр определен следующим образом:

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

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

typedef union _IIDR_ {

   struct  {

      unsigned char no_inter : 1;
      unsigned char inter_id : 2;
      unsigned char reserv : 5;

   } bit_reg;

   unsigned char byte;

} IIDR;

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

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

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

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

// управляющий регистр

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

typedef union _LCR_ {

   struct {

      unsigned char len : 2;
      unsigned char stop : 1;
      unsigned char parity : 2;
      unsigned char stuck_parity : 1;
      unsigned char en_break_ctl : 1;
      unsigned char dlab : 1;


   } bit_reg;

   unsigned char byte;

} LCR;

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

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

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

 7 6 5 4 3 2 1 0
--T-T-T-T-T-T-T- 
                 
LT+-+T+T+T+T+T+T-
 L=T=-         L= Линия DTR. Сигнал подтверждения связи.
                  Используется модемами для разрешения
                  передачи данных между компьютером
                  и микросхемой UART
              
             L=== Линия RTS. Сигнал подтверждения связи.
                  Используется модемами для разрешения
                  передачи данных между компьютером
                  и микросхемой UART
            
           L===== Линия OUT1 (запасная). Для некоторых
                  модемов при установке этого бита в единицу
                  происходит его аппаратный сброс
          
         L======= Линия OUT2 (запасная). Если бит D3
                  содержит единицу, то UART может
                  вырабатывать прерывания, а если нулю -
                  не может
        
       L========= Запуск диагностики при входе
                  асинхронного адаптера, замкнутом
                  на его выход (Digital Loopback test). Эта
                  возможность реализована только для
                  асинхронных портов, использующих
                  микросхему UART 8250, или полностью
                  совместимых с ней
    
   L============= Должны быть равны 0

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

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

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

typedef union  _MCR_ {

   struct {

      unsigned char dtr : 1;
      unsigned char rts : 1;
      unsigned char out1 : 1;
      unsigned char out2 : 1;
      unsigned char diag : 1;
      unsigned char reserv : 3;

   } bit_reg;

   unsigned char byte;

} MCR;

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

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

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

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

// регистр состояния линии

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

typedef union _LSR_ {

   struct {

      unsigned char in_ready : 1;
      unsigned char overflow : 1;
      unsigned char parity : 1;
      unsigned char synxr : 1;
      unsigned char break_detect : 1;
      unsigned char out_ready : 1;
      unsigned char shift_ready : 1;
      unsigned char taimout : 1;

   } bit_reg;

   unsigned char byte;

} LSR;

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

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

 7 6 5 4 3 2 1 0
--T-T-T-T-T-T-T- 
                 
LT+T+T+T+T+T+T+T-
               L= Линия CTS изменила состояние
              
             L=== Линия DSR изменила состояние
            
           L===== Линия RI изменила состояние. Некоторые
                  коммуникационные программы определяют по
                  состоянию этого бита наличие звонка на
                  телефонной линии
          
         L======= Если данный бит равен единице, значит
                  линия DCD изменила свое состояние.
                  Некоторые коммуникационные программы
                  определяют по состоянию этого бита,
                  детектировал ли модем несущую частоту на 
                  телефонной линии
        
       L========= Состояние линии CTS. Эта линия
                  используется совместно с линией RTS при
                  реализации аппаратного управления потоком
                  данных
      
     L=========== Состояние линии DSR. Эта линия
                  используется совместно с линией DTR при
                  аппаратной реализации подтверждения связи
    
   L============= Состояние линии RI. Единица означает, что
                  модем обнаружил звонок на телефонной
                  линии
  
 L=============== Состояние линии DCD. Единица означает, что
                  модемом получена несущая частота. Заметим,
                  что при выполнении аналогового теста
                  (Analog test) этот бит должен содержать
                  единицу. Если это не так, то возможно, что
                  модем исправен (для внешних модемов), но
                  кабель, соединяющий модем и компьютер, не
                  полностью соответствует стандарту RS-232

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

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

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

typedef union _MSR_ {

   struct {

      unsigned char change_cts : 1;
      unsigned char change_dsr : 1;
      unsigned char change_ri : 1;
      unsigned char change_dcd : 1;
      unsigned char cts : 1;
      unsigned char dsr : 1;
      unsigned char ri : 1;
      unsigned char dcd : 1;

   } bit_reg;

   unsigned char byte;

} MSR;

В качестве примера рассмотрим программу, которая считывает и отображает на дисплее состояние некоторых регистров асинхронного адаптера. Сначала программа запрашивает номер COM-порта, состояние регистров которого вы хотите узнать. Затем она получает базовый адрес регистров этого асинхронного адаптера. Базовый адрес регистров данного асинхронного адаптера программа получает через область переменных BIOS. Вычисление базового адреса выполняется функцией com_address():

#include "sysp_com.h"

/**
*.Name         com_address
*
*.Title        Определяет адрес заданного COM-порта.
*
*.Descr        Эта функция определяет адрес базового регистра
*              COM-порта. Адрес берется из области переменных
*              BIOS.
*
*.Proto        unsigned  com_address( int port );
*
*.Params       int port - номер асинхронного адаптера:
*              0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4.
*
*.Return       Адрес базового регистра асинхронного порта.
*              Если порт не установлен, возвращается 0,
*              если неправильно задан параметр, то -1.
*
*.Sample       com_adr.c
**/

unsigned  com_address( int port ) {

   unsigned base_address;

   // возвращаем -1, если заданный асинхронный порт
   // не COM1, не COM2, не COM3 и не COM4

   if(( port > 4 ) || ( port < 0 )) return( -1 );

   // считываем из области переменных BIOS базовый адрес данного порта

   base_address = *(( unsigned _far * ) FP_MAKE( 0x40, port * 2 ));

   return( base_address );
}

Старые версии BIOS могут самостоятельно определить наличие и адреса только первых двух COM-портов. Таким образом, если на вашем компьютере установлен асинхронный последовательный порт COM3 или COM4, то без дополнительной инициализации данная программа может просто "не увидеть" эти порты.

После определения базового адреса COM-порта, программа считывает состояние регистров данного порта и отображает их на дисплее.

Итак, приведем основной модуль программы:

// REGISTER.C

#include   "uart_reg.h"
#include   "sysp_com.h"
#include   <graph.h>


unsigned com_address( int );

void main(void) {

   unsigned adr;
   int port;
   unsigned char data;

   LCR     reg_lcr;
   LSR     reg_lsr;
   MCR     reg_mcr;
   MSR     reg_msr;
   ICR     reg_icr;
   IIDR    reg_iidr;

   printf(" Программа отображает состояние регистров UART.\n\n");

   printf(" Введите номер асинхронного порта (1-4): ");
   scanf("%d", &port );

   // определяем базовый адрес адаптера port

   adr = com_address( --port );

   // считываем значение управляющего регистра

   printf("\n Управляющий регистр = %Xh\n\n", data =
        (unsigned char ) inp( adr + LCR_N ) );

   reg_lcr.byte = data;

   printf("    Длина слова в байтах %d\n"
        "    Количество стоповых битов %d\n"
        "    Контроль %s\n"
        "    Фиксация четности %s\n"
        "    Установка перерыва: %s\n"
        "    Порты %Xh, %Xh используются %s\n",

         reg_lcr.bit_reg.len + 5,
         reg_lcr.bit_reg.stop + 1,
         (reg_lcr.bit_reg.parity == 3) ? "на четность" :
            ((reg_lcr.bit_reg.parity == 1 ) ? "на нечетность" :
                  "четности не выполняется"),

         (reg_lcr.bit_reg.stuck_parity == 1) ? "производится" :
                  "не производится",

         (reg_lcr.bit_reg.en_break_ctl == 1) ? "есть" :
                  "нет",

         adr, adr + 1,
         (reg_lcr.bit_reg.dlab == 1) ? "для загрузки делителя частоты" :
                  "как обычно"

   );


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

   printf("\n Регистр состояния линии = %Xh\n\n", data =
        (unsigned char ) inp( adr + LSR_N ) );

   reg_lsr.byte = data;

   printf("    Данные готовы для чтения: %d\n"
        "    Ошибка переполнения: %d\n"
        "    Ошибка четности: %d\n"
        "    Ошибка синхронизации: %d\n"
        "    Обнаружен запрос на прерывание \"BREAK\": %d\n"
        "    Регистр хранения передатчика пуст: %d\n"
        "    Регистр сдвига передатчика пуст: %d\n"
        "    Тайм-аут: %d\n",

         reg_lsr.bit_reg.in_ready,
         reg_lsr.bit_reg.overflow,
         reg_lsr.bit_reg.parity,
         reg_lsr.bit_reg.synxr,
         reg_lsr.bit_reg.break_detect,
         reg_lsr.bit_reg.out_ready,
         reg_lsr.bit_reg.shift_ready,
         reg_lsr.bit_reg.taimout
   );

   printf("\n\n Нажмите любую клавишу для продолжения ");
   getch();


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

   printf("\n Регистр управления модемом = %Xh\n\n", data =
        ( unsigned char ) inp( adr + MCR_N ));
   reg_mcr.byte = data;


   printf("    Линия dtr: %d\n"
        "    Линия rts: %d\n"
        "    Линия out1: %d\n"
        "    Линия out2: %d\n"
        "    Запуск диагностики: %d\n",

         reg_mcr.bit_reg.dtr,
         reg_mcr.bit_reg.rts,
         reg_mcr.bit_reg.out1,
         reg_mcr.bit_reg.out2,
         reg_mcr.bit_reg.diag
   );

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

   printf("\n Регистр состояния модема = %Xh\n\n", data =
        ( unsigned char ) inp( adr + MSR_N ) );
   reg_msr.byte = data;


   printf("    Линия изменила cts состояние: %d\n"
        "    Линия изменила dsr состояние: %d\n"
        "    Линия изменила ri состояние: %d\n"
        "    Линия изменила dcd состояние: %d\n"
        "    Линия cts: %d\n"
        "    Линия dsr: %d\n"
        "    Линия ri: %d\n"
        "    Линия dcd: %d\n",

         reg_msr.bit_reg.change_cts,
         reg_msr.bit_reg.change_dsr,
         reg_msr.bit_reg.change_ri,
         reg_msr.bit_reg.change_dcd,
         reg_msr.bit_reg.cts,
         reg_msr.bit_reg.dsr,
         reg_msr.bit_reg.ri,
         reg_msr.bit_reg.dcd
   );


   printf("\n\n Нажмите любую клавишу для продолжения ");
   getch();

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

   printf("\n\n Регистр управления прерываниями = %Xh\n\n", data =
        ( unsigned char ) inp( adr + ICR_N ));

   reg_icr.byte = data;


   printf("    Разрешение прерывания при готовности принимаемых данных: %d\n"
        "    Разрешение прерываний после передачи данных: %d\n"

        "    Разрешение прерывания при обнаружении"
        "    состояния \"BREAK\": %d\n"

        "    Разрешение прерыывания при изменении состояния линий"
        " cts, dsr, ri, dcd: %d\n",

         reg_icr.bit_reg.in_ready,
         reg_icr.bit_reg.out_ready,
         reg_icr.bit_reg.err,
         reg_icr.bit_reg.change
   );


   // считываем значение регистра идентификации прерываний

   printf("\n\n Регистр идентификации прерываний = %Xh\n\n", data =
        ( unsigned char ) inp( adr + IIDR_N ));

   reg_iidr.byte = data;


   printf("    Нет прерываний, ожидающих обслуживания: %d\n"
        "    Идентификатор прерывания: %d\n",

         reg_iidr.bit_reg.no_inter,
         reg_iidr.bit_reg.inter_id
   );
}

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

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

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

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

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

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

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

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

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

void aux_stat(AUX_MODE *mode, int port) {

   unsigned long b;

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

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

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

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

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

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

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

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

   mode->baud = b;

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

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

}

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

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

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

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

   unsigned div;
   char ctl;

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

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

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

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

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

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

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

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

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

   return(0);

}

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

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

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

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

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


void aux_outp(char chr, int port) {

   unsigned status_reg, out_reg;

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

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

   outp(out_reg, chr);

}

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

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

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

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

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

char aux_inp(int port) {

   unsigned status_reg, inp_reg;

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

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

   return(inp(inp_reg));

}

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

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

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

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

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

   AUX_MODE amd;

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

   amd.baud = 115200;

   aux_init(&amd, 0, 0);

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

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

   for(;;) {

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

      aux_outp(getch(), 0);

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

      putchar(aux_inp(0));
   }
}

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

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

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

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

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

mov   al, 20h
out   20h, al

iret

В противном случае возможно "зависание" программы.

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

Ниже мы приведем некоторые полезные сведения о контроллере прерываний.

Контроллер прерываний

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void (* interrupt_table[256])();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Наиболее интересными с точки зрения программирования контроллера прерываний являются регистры маски прерываний IMR и управляющий регистр прерываний.

В машинах класса XT регистр маски прерываний имеет адрес 21h, управляющий регистр прерываний - 20h. Для машин AT первый контроллер 8259 имеет такие же адреса, что и в машинах XT, регистр маски прерываний второго контроллера имеет адрес A1h, управляющий регистр прерываний - A0h.

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

Приведем пример программы, маскирующей прерывание от флоппи-диска:

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

void main(void);

void main(void) {

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

   exit(0);

}

Чтобы "оживить" флоппи-диски, запустите программу, которая размаскирует все прерывания (в том числе и от флоппи):

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

void main(void);

void main(void) {

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

   exit(0);

}

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

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

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

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

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

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

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

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

Контроллер прерываний 8259

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

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

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

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

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

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

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

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

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

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

  • индивидуальное маскирование запросов прерывания;
  • специальное маскирование обслуженных запросов;
  • установка статуса уровней приоритета (по установке исходного состояния, по обслуженному запросу, по указанию);
  • операции конца прерывания (обычный конец прерывания, специальный конец прерывания, автоматический конец прерывания);
  • чтение регистров IRR, ISR, IMR.

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

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

  • 1. Маскирование запросов прерывания.
  • 2. Команды обработки конца прерывания.
  • 3. Опрос регистров и специальное маскирование.

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

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

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

Биты байта команды
D7 D6 D5 D4 D3 D2 D1 D0



Описание
0  0  1  0  0  0  0  0



Обычный конец прерывания
0  1  1  0  0  B2 B1 B0



Специальный конец прерывания, B0...B2 - двоично-десятичный код сбрасываемого разряда в регистре обслуживания прерывания ISR
1  0  1  0  0  X  X  X



Циклический сдвиг уровней приоритета с обычным концом прерывания. Дно приоритетного кольца устанавливается по обслуженному запросу
1  1  1  0  0  B2 B1 B0



Циклический сдвиг уровней приоритета со специальным концом прерывания, B0...B2 - двоично-десятичный код дна приоритетного кольца
1  0  0  0  0  X  X  X



Разрешение вращения уровней приоритета
0  0  0  0  0  X  X  X



Сброс разрешения вращения уровней приоритета
1  1  0  0  0  B2 B1 B0



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

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

Биты байта команды
D7 D6 D5 D4 D3 D2 D1 D0

Описание
0  0  0  0  1  1  X  X

Установка режима опроса
0  0  0  0  1  0  1  1

Разрешение чтения регистра ISR
0  0  0  0  1  0  1  0

Разрешение чтения регистра IRR
0  1  1  0  1  0  0  0

Разрешение триггера специального маскирования
0  1  0  0  1  0  0  0

Сброс триггера специального маскирования

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 7 6 5 4 3 2 1 0
--T-T-T-T-T-T-T- 
                 
LT+-+T+T+T+T+T+T-
 L=T=- L=    L= = Длина слова в битах:
                    00 - 5 бит;
                    01 - 6 бит;
                    10 - 7 бит;
                    11 - 8 бит
            
           L===== Количество стоповых бит:
                    0 - 1 бит;
                    1 - 2 бита
          
         L======= Четность:
                    x0 - контроль на четность не
                         используется;
                    01 - контроль на нечетность;
                    11 - контроль на четность
    
   L============= Скорость передачи данных в бодах:

                    000 - 110
                    001 - 150
                    010 - 300
                    011 - 600
                    100 - 1200
                    101 - 2400
                    110 - 4800
                    111 - 9600

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2.8. Программирование асинхронного адаптера средствами MS-DOS

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

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

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

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

2.9. Стандартные функции библиотеки Си для работы с последовательным портом

К сожалению, библиотеки трансляторов Microsoft Quick C 2.5 и C 6.0, а также трансляторов Borland C++ и Turbo C содержат всего одну функцию управления последовательным портом ввода/вывода. Трансляторы Microsoft Quick C 2.5 и C 6.0 содержат функцию _bios_serialcom(), а Borland C++ и Turbo C - функцию bioscom().

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

Заметим, что функции _bios_serialcom() и bioscom() работают только на компьютерах, полностью совместимых с IBM PC/XT/AT.

При использовании функций _bios_serialcom() и bioscom() необходимо включить директивой #include файл bios.h. Для трансляторов фирмы Borland этот файл включает объявление функции bioscom(), а для Microsoft кроме объявления функции _bios_serialcom() - также определения констант, которые можно использовать с этой функцией.

Рассмотрим функцию _bios_serialcom():

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

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

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

_COM_INIT инициализация последовательного порта
_COM_RECEIVE принять байт
_COM_SEND передать байт
_COM_STATUS определить состояние порта

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

int main(void) {

   unsigned in, out, status;

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

   _bios_serialcom(_COM_INIT, COM1, _COM_1200 |
                        _COM_CHR8 | _COM_STOP1);

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

   for(;;)  {

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

      status = _bios_serialcom(_COM_STATUS, COM1, 0);

      // если данные готовы, считываем их из
      // последовательного порта и выводим на экран  дисплея

      if(status & DATA_READY)
         if((out = _bios_serialcom(_COM_RECEIVE, COM1, 0) &
                                 0x7F) != 0)
            putch(out);

      //  проверяем, не нажата ли клавиша на клавиатуре?

      if(kbhit()) {

         // если нажата клавиша [ESC] выходим из программы

         if((in = getch()) == 0x1b)
            break;

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

         _bios_serialcom(_COM_SEND, COM1, in);
      }
   }
   return(0);
}

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

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

Эта функция аналогична функции _bios_serialcom(), трансляторов Microsoft Quick C 2.5 и C 6.0, за исключением следующих моментов:

  • отличается порядок следования аргументов функции;
  • не соответствуют типы аргументов, имеющие одинаковый смысл;
  • для трансляторов Borland C++ и Turbo C во включаемом файле bios.h отсутствует определение констант _COM_xxx.

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

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

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

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

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

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

Так как _COM_xxx константы не определены, то для совместимости с трансляторами Microsoft и для удобства мы можем определить их самостоятельно:

// BC_CONST.H
// определение констант для Turbo C и Borland C++

#define _COM_INIT      0
#define _COM_SEND      1
#define _COM_RECEIVE   2
#define _COM_STATUS      3

#define _COM_CHR7      0x02
#define _COM_CHR8      0x03
#define _COM_STOP1      0x00
#define _COM_STOP2      0x04

#define _COM_NOPARITY   0x00
#define _COM_EVENPARITY   0x18
#define _COM_ODDPARITY   0x08

#define _COM_110      0x00
#define _COM_150      0x20
#define _COM_300      0x40
#define _COM_600      0x60
#define _COM_1200      0x80
#define _COM_2400      0xa0
#define _COM_4800      0xc0
#define _COM_9600      0xe0

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

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

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

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

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

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

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

Приведем небольшой пример использования функции bioscom(). Данная программа проверяет состояние линий DSR и CTS для асинхронных портов COM1..COM4. Если обе линии DSR и CTS находятся в активном состоянии, значит устройство, подключенное к данному порту готово к работе (активно).

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

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

#include "bc_const.h"  // определяем констаны _COM_xxx


void main(void) {

   unsigned status, port;

   for( port = 0; port < 4; port++ )    {

      status = bioscom( _COM_STATUS, 0, port, );

      // Проверяем состояние каждого последовательного порта
      // и определяем наличие присоединенных к нему устройств
      // типа модема.
      // Считаем, что если биты data-set-ready и clear-to-send 
      // установлены в единицу, то внешнее устройство отвечает.

      printf( "COM%c состояние: %.4X\tАктивный: %s\n",
            (char)port + '1', status,
            (status & 0x0030) ? "Да" : "Нет" );
   }
}

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

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

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

  • 8250 (8250-B): Использовался на первых моделях IBM PC
  • 16450/(8250-A): Эта микросхема используется в основном для IBM PC/AT, так как имеет большую производительность. Фактически это 8250, но изготовленный с использованием новой технологии. Эта микросхема дополнена регистром расширения (scratch register), имеющим адрес 3FFh (base_adr + 7). В ней также устранены ошибки в регистре разрешения прерываний и добавлена возможность перевода линии OUT2 в высокоимпедансное состояние во время проведения тестов, когда выход данных замкнут на вход
  • 16550: Фактически соответствует 16450. Добавлена возможность внутренней буферизации передаваемых и принимаемых данных. Буфера выполнены по схеме FIFO (First In First Out - первый вошел, первым вышел) или, другими словами, в виде очереди. При использовании буферизации возможно заметно уменьшить число прерываний, вырабатываемых асинхронным портом. Однако из-за ошибки в микросхеме эту возможность лучше не использовать - можно потерять отдельные символы. В общем случае микросхема 16550 более быстрая, чем 16450. Дополнительно 16550 дает возможность использовать несколько каналов прямого доступа (DMA channels)
  • 16550A (16550AN) Соответствует 16550, исправлены ошибки реализации FIFO. Эта микросхема дает возможность использования программисту нескольких каналов прямого доступа (DMA channels). 16550A, как правило, используется в компьютерах с процессорами 80386/486 и в компьютерах с RISC-архитектурой. Заметим, что, если вы хотите работать на скоростях больших, чем 9600 бод, вам желательно использовать именно эту микросхему.

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

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

  • UART 8250 не имеет регистра расширения.
  • UART 16450 не имеет внутренних буферов FIFO.
  • UART 16550 имеет внутренние буфера FIFO, но с ошибками. Бит D7 регистра управления прерываниями (IIR) равен единице, а бит D6 - нулю.
  • UART 16550A не содержит ошибок при реализации FIFO. Биты D7 и D6 регистра управления прерываниями (IIR) равны единице.

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

  • Читаем и сохраняем значение регистра расширения. Адрес регистра расширения определяем как базовый адрес плюс семь (base_adr + 7)
  • Записываем в регистр расширения какое-либо число, например 0A5h
  • Снова считываем значение регистра расширения и сравниваем его с числом, ранее записанным в него (0A5h). Если эти значения не равнозначны, значит регистр расширения отсутствует и, следовательно, тестируемая микросхема - UART 8250
  • Затем опять запоминаем в регистре расширения другое число, например 5Ah
  • Снова считываем значение регистра расширения и сравниваем его с ранее записанным числом. Если эти значения не одинаковы, значит регистр расширения отсутствует и следовательно, тестируемая микросхема - UART 8250
  • Восстанавливаем величину, изначально хранившуюся в регистре расширения
  • Считываем и сохраняем регистр управления прерываниями
  • Запоминаем единицу в регистре FCR (регистр управления режимом буферизации, подробно описан ниже)
  • Считываем значение регистра управления прерываниями. Если бит D7 сохраненного регистра управления прерываниями очищен, запоминаем единицу в регистре FCR
  • Если бит D6 регистра управления прерываниями содержит единицу, тестируемая микросхема - UART 16550A
  • Если бит D7 регистра управления прерываниями содержит единицу, тестируемая микросхема - UART 16550
  • В противном случае тестируемая микросхема - UART 16450.

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

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

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


void main(void) {

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

   int port = 0;

   int test;

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

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

   // проверяем, является ли микросхема UART - UART 8250

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

   // проверяем другие типы микросхем UART

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

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

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


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

   int save_scr, in_scr;

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

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

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

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

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

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

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

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

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

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

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

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



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

   int save_iir, in_iir;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 7 6 5 4 3 2 1 0
--T-T-T-T-T-T-T- 
                 
LT+T+T+T+T+T+T+T-
     LT-       L= Разрешение буферизации. При D0, равном
                  единице, буферизация разрешена
              
             L=== Сброс приемного буфера
            
           L===== Сброс буфера передатчика
          
         L======= Выбор режима прямого доступа
       
      L========== Не используются
    
   L============= Триггер приемника (LSB)
  
 L=============== Триггер приемника (MSB)
  • D0. Установка этого бита в единицу разрешает использование буферизации для принимаемых и передаваемых данных. Этот бит должен содержать единицу, если какой-либо из других битов содержит единицу.
  • D1. При установке этого бита в единицу буфер приемника очищается. Затем бит автоматически сбрасывается в ноль.
  • D2. При установке этого бита в единицу буфер передатчика очищается. Затем бит автоматически сбрасывается в ноль.
  • D3. Бит не используется на большинстве последовательных асинхронных адаптерах.
  • D7, D6. Управление прерываниями от приемника. Если буферизация отсутствует, то прерывание происходит всякий раз при приеме нового символа. С разрешенной буферизацией UART может генерировать прерывание при получении заданного количества символов:

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

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

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

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

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

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

  После обычной инициализации UART надо разрешить использование буферизации, записав в регистр управления буферизацией число 0C7h. При этом будет разрешено использование буферизации, произведена очистка буферов приемника и передатчика, а также вызвана генерация прерываний при записи в буфер приемника больше 14 символов. После этого следует прочитать содержимое регистра идентификации прерываня (по тому же адресу). Если бит D6 этого регистра не установлен, то ваша микросхема UART не является 16550A. И вам следует запретить использование буферизации, записав ноль в регистр управления буферизацией.

Назад       Содержание       Вперёд