Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование модемов

© Александр Фролов, Григорий Фролов
Том 4, М.: Диалог-МИФИ, 1993, 236 стр.

[Назад] [Содеожание] [Дальше]

5.3. Коммуникационная программа

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

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

Итак, приступим. Как мы сказали ранее, первым шагом при программировании модема надо считать инициализацию COM-порта (микросхемы UART), к которому подключен модем.

Инициализация COM-порта

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

mov al,0 ; сбрасываем сигналы DTR и RTS
mov dx,MCR ; где MCR - адрес регистра управления модемом
out dx,al
jmp $+2 ; задержка

Затем сбрасываем регистры состояния линии, состояния модема и данных. Это достигается простым считыванием значений этих регистров:

; сбрасываем регистр состояния линии

mov   dx,LSR   ; LSR - адрес регистра состояния линии
in    al,dx
jmp   $+2      ; задержка


; сбрасываем регистр состояния модема

mov   dx,MSR   ; где MSR - адрес регистра состояния модема
in    al,dx
jmp   $+2      ; задержка

; сбрасываем регистр данных

mov   dx,DAT   ; где DAT - адрес регистра данных
in    al,dx
jmp   $+2      ; задержка

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

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

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

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

   ctl = inp(LCR);      // LCR - адрес регистра управления
   outp(LCR_N, ctl | 0x80);   // устанавливаем старший бит регистра

// вычисляем значение для делителя частоты (переменная baud
// определяет скорость обмена, которую мы хотим установить)

   switch(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;
   }

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

   outp(ICR, (div >> 8) & 0x00ff);   // ICR - адрес регистра
                        // управления прерываниями

   outp(DAT, div & 0x00ff);         // DAT - адрес регистра
                        // данных

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

   ctl = inp(LCR);      // LCR - адрес регистра управления
   outp(LCR, ctl & 0x7f);   // сбрасываем старший бит регистра

Затем надо определить формат данных. Для этого записываем новое управляющее слово в управляющий регистр:

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

   outp(LCR, 00000011B);

// управляющее слово 00000011B устанавливает длину слова 8 бит, 
// один стоповый бит, отменяет проверку на четность и отменяет
// режим фиксации четности (см. главу "Порты асинхронного адаптера")

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

Чтобы запретить генерацию прерываний, надо просто записать значение ноль в регистр управления прерываниями:

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

   outp(port_adr+ICR, 0);   // ICR - адрес регистра
                  // управления прерываниями

На этом этап инициализации регистров UART можно считать законченным. Теперь COM-порт подготовлен для обмена через него данными с модемом, но модем пока еще не будет воспринимать данные от компьютера. Чтобы перевести его в рабочее состояние, ему передаются сигналы DTR и RTS, сообщающие, что компьютер готов к обмену данными. В ответ на эти сигналы модем должен вернуть компьютеру сигналы DSR и CTS (см. главу "Аппаратная реализация"):

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

   mcr = inp( MCR );   // MCR - адрес регистра управления модемом

   // устанавливаем сигналы DTR и RTS в активное состояние

   mcr |= 3;

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

   outp( MCR, mcr );

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

Инициализация модема и установление связи

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

При инициализации модема с помощью AT-команд можно установить различные параметры и режимы работы модема. Например, можно управлять формой сообщений от модема (цифровая или словесная), включать и выключать динамик модема и т.д. Список таких команд представлен в главе "Система команд hayes-модемов".

Модем выполняет переданные ему команды и возвращает ответ, который вы можете прочитать через COM-порт, к которому подключен модем. Сообщения модема представлены в главе "Система команд hayes-модемов".

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

Активный вызов удаленного модема

Для активного вызова модемом абонента надо послать модему соответствующую AT-команду. Например, для набора номера 926-76-34 модему посылается следующая команда:

AT DP 926-76-34 <CR>

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

Ниже приведен фрагмент кода, который передает модему символы, принятые от клавиатуры и отображает на экране символы, принятые от модема:

while( 1 ) {
   if( kbhit() ) {

      // если нажата клавиша клавиатуры, считываем ее код

      key =  getch();

      // по нажатию клавиши Esc выходим из данной функции

      if( key == 27 )
         exit(0);

      // если пользователь нажал Enter, передаем модему
      // символ перевода строки и возврата каретки

      if( key == '\r' ) 

         // посылаем символ в COM-порт
         com_out( com_adr, 0xd );

      else {

         // отображаем символ на экране
         putch( key );

         // посылаем символ в COM-порт
         com_out( com_adr, key );
      }
   }

   // если получены данные от модема, отображаем их на экране

   if( from_modem( com_adr, &ch_in ) == 0 )
      putch( ch_in );
}

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

Режим автоответа

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

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

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

Обмен данными с удаленным модемом

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

Исходный текст коммуникационной программы S_CHAT

В этой главе мы объединим все сказанное выше в одной программе. Программа состоит из следующих модулей:

S_CHAT.C      // главная процедура программы
COM_ADR.C      // определение базового адреса регистров COM-порта
RESET.C      // сброс регистров микросхемы UART
COM_INIT.C   // инициализация COM-порта
DTR.C      // управление сигналами DTR и RTS
TO_MODEM.C   // передача данных модему через COM-порт
EXCHANGE.C   // организация диалога с удаленным модемом
FROM_MDM.C   // прием данных от модема через COM-порт
DISP.C      // функции для работы с видеопамятью
TIMER.C      // реализация временных задержек

Рассмотрим подробнее каждый модуль программы. Самый верхний уровень представляет модуль S_CHAT.C. Он содержит определение главной процедуры программы S_CHAT.

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

Модуль S_CHAT.C - это центральный модуль программы, он выполняет все действия по программированию модема и обмена данными с ним, вызывая функции из других модулей. Сначала процедура main() сбрасывает регистры микросхемы UART, вызывая функцию reset() из модуля RESET.C. После того как выполнен сброс регистров, вызывается функция com_init() из модуля COM_INIT.C, которая устанавливает скорость обмена и формат данных - число стоповых бит и режим проверки по четности.

Затем выполняется функция dtr_on(), определенная в модуле DTR.C. Эта функция посылает сигналы DTR и RTS, сообщающие модему о готовности компьютера к обмену данными.

На этом подготовительный этап можно считать завершенным. Теперь уже можно передавать модему данные через COM-порт. Так как при включении питания модем находится в командном режиме, то мы можем передавать ему AT-команды и устанавливать связь с удаленным модемом.

В этой программе при помощи функции to_modem(), определенной в модуле TO_MODEM.C, на модем подается команда "AT M1 DP 251 2762". Эта команда включает динамик модема (AT M1) и набирает номер (AT DP 251 2762). Если модем набрал номер, он переходит в режим обмена данными с удаленным модемом, а если связь установить не удалось (занят номер), модем остается в командном режиме.

Далее, независимо от того, установил модем связь или нет, вызывается функция exchange(), определенная в модуле EXCHANGE.C, которая позволяет передавать модему данные, набранные на клавиатуре, а принятые от модема данные отображать на экране дисплея. При этом нет разницы в том, в каком режиме находится модем. Если он в командном режиме, данные, введенные с клавиатуры, будут восприниматься модемом как команды, а если модем в режиме обмена данными - как данные (т.е. будут передаваться удаленному модему).

Вы можете убрать из программы передачу команды набора номера и сразу после установки сигналов DTR и RTS передавать управление функции exchange(). В этом случае для набора номера вам надо самому ввести с клавиатуры команду ATDP и нужный номер, а затем нажать клавишу Enter.

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

Перевод модема в командный режим осуществляется передачей ему специальной Escape-последовательности "+++". Для этого сначала выполняется временная задержка 2,5 секунды (продолжительность задержки определяется регистром модема S12, по умолчанию одна секунда). Затем при помощи функции com_out() из модуля TO_MODEM.C модему передаются три знака '+' и опять выполняется временная задержка. Временная задержка выполняется функцией delay(), определенной в модуле TIMER.C:

delay(2500);

com_out( com_adr, '+' );
com_out( com_adr, '+' );
com_out( com_adr, '+' );

delay(2500);

После того как модем положил трубку, программа сбрасывает сигналы DTR и RTS. На этом выполнение программы завершается.

Итак, модуль S_CHAT.C:

// S_CHAT.C
// простая терминальная программа


#include <conio.h>
#include <time.h>
#include <graph.h>
#include "timer.h"
#include "sysp_com.h"

// используется COM-порт номер 3, для использования
// другого COM-порта измените эту директиву

#define COM_PORT  2 // COM3

// объявления функций

unsigned    com_address( int );
int         to_modem( unsigned, char* );
int         dtr_on( unsigned );
int         reset( unsigned );
void        disp( char, char );
void        disp_string( char*, char );
void        exchange( unsigned com_adr );

//
// Главная процедура
//

void main(void) {

   AUX_MODE amd;
   unsigned com_adr;
   char     ch_in;
   int      i, mdm_sts;


   // устанавливаем текстовый режим 25*80 символов
   _setvideomode( _TEXTC80 );

   // очищаем экран дисплея
   _clearscreen( _GCLEARSCREEN );

   // гасим курсор
   _displaycursor( _GCURSOROFF );

   disp_string( "(C) Frolov G.V. Телекоммуникационная программа\n\r\n\r", 4 );

   // получаем базовый адрес регистров порта COM_PORT

   if(( com_adr = com_address( COM_PORT )) < 0 ) exit( com_adr );

   // сбрасываем регистры UART

   reset( com_adr);

   // инициализируем COM-порт: устанавливаем скорость и
   // формат данных

   amd.baud = 1200L;                      // скорость обмена
   amd.ctl_aux.ctl_word.len = 3;          // длина слова

   amd.ctl_aux.ctl_word.stop = 0;         // число стоп-битов
   amd.ctl_aux.ctl_word.parity = 0;       // контроль четности
   amd.ctl_aux.ctl_word.stuck_parity = 0; // фиксация четности
   amd.ctl_aux.ctl_word.en_break_ctl = 0; // установка перерыва
   amd.ctl_aux.ctl_word.dlab = 0;         // загрузка регистра делителя

   // производим инициализацию COM-порта с базовым адресом com_adr

   com_init(&amd, com_adr, 0);

   // устанавливаем сигнал DTR и RTS

   dtr_on( com_adr );


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

   disp_string( "\n\rВы можете вводить AT-команды, для выхода нажмите ESC\n\r", 12 );

   disp_string( "\n\rНабираем номер\n\r", 12 );
   if( 0!= to_modem( com_adr, "AT M1 DP 251 27 62" )) exit(3);

   // задержка

   sleep(1);

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

   exchange(  com_adr );
   disp_string( "\n\rПодождите, я кладу трубку\n\r", 12 );

   // передаем модему Escape-последовательность

   delay(3000);
   com_out( com_adr, '+' );
   com_out( com_adr, '+' );
   com_out( com_adr, '+' );
   delay(3000);

   // кладем трубку

   to_modem( com_adr, "ATH0" );
   sleep(1);

   // сбрасываем сигналы DTR и RTS

   dtr_off( com_adr );

   disp_string( "\n\r\n\rКонец работы\n\r", 4 );
   _setvideomode( _DEFAULTMODE );
}

Обратите внимание, что в этой программе жестко указан номер используемого COM-порта, к которому подключается модем. Вам надо перед трансляцией программы изменить в модуле S_CHAT директиву:

#define COM_PORT 2 // используется порт COM3

Указав вместо 2 номер порта, к которому у вас подключен модем. Для порта COM1 надо определить константу COM_PORT как 0, для COM2 - 1, COM3 - 2, COM4 - 3.

По номеру COM-порта, указанному вами, функция com_address() из модуля COM_ADR.C определит адрес базового регистра данного COM-порта. Вычисление базового адреса COM-порта производится в соответствии с областью переменных BIOS:

// COM_ADR.C

#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.
**/

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 );
}

Модуль RESET.C содержит определение функции reset(). Функция reset() сбрасывает значения регистров управления модемом, состояния линии, состояния модема и данных.

// RESET.C

#include "uart_reg.h"

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

int reset(unsigned com_adr) {

unsigned MCR, LSR, MSR, DATREG;

   MCR = com_adr + MCR_N;
   LSR = com_adr + LSR_N;
   MSR = com_adr + MSR_N;
   DATREG = com_adr;

   _asm {

      cli

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

      mov   al,0
      mov   dx,MCR
      out   dx,al
      nop
      nop
      nop

      ; сбрасываем регистр состояния линии

      mov   dx,LSR
      in    al,dx
      nop
      nop
      nop

      ; сбрасываем регистр данных

      mov   dx,DATREG
      in    al,dx
      nop
      nop
      nop

      ; сбрасываем регистр состояния модема

      mov   dx,MSR
      in    al,dx
      nop
      nop
   }
}

Модуль COM_INIT.C содержит определение функции com_init(), которая используется нами для инициализации регистров COM-порта. Этой функции вы должны передать структуру типа AUX_MODE (определена в файле sysp_com.h), поля которой определяют скорость обмена и формат данных:

// COM_INIT.C

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

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


int com_init(AUX_MODE *mode, int port_adr, 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(port_adr+LCR_N);
   outp(port_adr+LCR_N, ctl | 0x80);

   outp(port_adr+ICR_N, (div >> 8) & 0x00ff);
   outp(port_adr, div & 0x00ff);

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

   outp(port_adr+LCR_N, mode->ctl_aux.ctl & 0x7f);

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

   outp(port_adr+ICR_N, imask);

   return(0);
}

Для управления сигналами DTR и RTS в модуле DTR.C определены две функции - dtr_on() и dtr_off(). Функция dtr_on() устанавливает сигналы DTR и RTS, а функция dtr_off() сбрасывает их.

// DTR.C

#include <conio.h>
#include "uart_reg.h"

/**
*.Name         dtr_on
*
*.Title        Устанавливает сигналы DTR и RTS.
*
*.Descr        Эта функция устанавливает сигналы DTR и RTS
*              для заданного COM-порта.
*
*.Proto        void dtr_on( unsigned base_address );
*
*.Params       unsigned base_address - базовый адрес асинхронного адаптера
*
*.Return       не используется
**/

void dtr_on( unsigned base_address ) {

   MCR mc_reg;

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

   mc_reg.byte = inp( base_address + MCR_N );

   // устанавливаем сигналы DTR и RTS в активное состояние

   mc_reg.bit_reg.dtr = mc_reg.bit_reg.rts = 1;

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

   outp( base_address + MCR_N, mc_reg.byte );
}




/**
*.Name         dtr_off
*
*.Title        Сбрасывает сигналы DTR и RTS.
*
*.Descr        Эта функция сбрасывает сигналы DTR и RTS
*              для заданного COM-порта.
*
*.Proto        void dtr_on( unsigned base_address );
*
*.Params       unsigned base_address - базовый адрес асинхронного адаптера
*
*.Return       не используется
**/

void dtr_off( unsigned base_address ) {

   MCR mc_reg;


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

   mc_reg.byte = inp( base_address + MCR_N );

   // сбрасываем сигналы DTR и RTS

   mc_reg.bit_reg.dtr = mc_reg.bit_reg.rts = 0;

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

   outp( base_address + MCR_N, mc_reg.byte );
}

Модуль TO_MODEM.C определяет функции to_modem() и com_out(). Эти функции используются для передачи модему данных через COM-порт.

Функция com_out() позволяет передать на модем только один символ. Передача символа осуществляется следующим образом:

  • ожидаем, пока модем сообщит о своей готовности по линии DSR;
  • ожидаем, пока освободится регистр передатчика и можно будет передать следующий байт;
  • записываем передаваемый байт в регистр данных COM-порта для последующей его передачи модему.

Функция to_modem() позволяет передать модему строку символов. После передачи последнего символа в строке дополнительно передается символ возврата каретки (ASCII-код 13). Эту функцию удобно использовать для передачи модему AT-команд.

Итак, приведем исходный текст модуля TO_MODEM.C:

// TO_MODEM.C

#include "sysp_com.h"
#include "uart_reg.h"

// объявления функций
int com_out( unsigned, char );
int to_modem( unsigned, char* );

/**
*.Name         to_modem
*
*.Title        Передает модему строку, оканчивающуюся нулем.
*
*.Descr        Эта функция передает модему строку, оканчивающуюся нулем.
*
*.Proto        void to_modem( unsigned base_address, char *out_str );
*
*.Params       unsigned base_address - базовый адрес асинхронного адаптера,
*              на котором находится модем;
*
*              char *out_str - массив символов, передаваемый модему,
*              заканчивается нулем
*
*.Return       -1, если нет сигнала DSR от модема
**/

int to_modem( unsigned base_address, char *out_str ) {

   int i;

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

   for( i = 0; out_str[i] != '\0'; i++ )
      if( 0 != com_out( base_address, out_str[i] )) return( -1 );

   // заканчиваем передачу строки и посылаем модему код
   // возврата каретки, вызывающий исполнение введенной команды

   com_out( base_address, 13 );
   return( 0 );
}


/**
*.Name         com_out
*
*.Title        Передает модему один символ.
*
*.Descr        Эта функция передает один символ.
*
*.Proto        void com_out( unsigned base_address, char out_char )
*
*.Params       unsigned base_address - базовый адрес асинхронного адаптера,
*              на котором находится модем;
*
*              char out_char - символ, передаваемый модему,
*
*.Return       -1, если нет сигнала DSR от модема
**/

int com_out( unsigned base_address, char out_char ) {

   unsigned char  next;

   int      i;
   LSR      ls_reg;
   MSR      ms_reg;

   // ожидаем, пока модем сообщит о своей готовности
   // по линии DSR

   for( ms_reg.byte = 0, i = 0;
         ((ms_reg.bit_reg.dsr == 0) && ( i < 1000)); i++) {

      ms_reg.byte = inp( base_address + MSR_N );
   }

   if( i == 1000 ) return( -1 ); // модем не готов


   // ожидаем, пока освободится регистр передатчика
   // и можно будет передать следующий байт

   for( ls_reg.byte = 0; ls_reg.bit_reg.out_ready == 0; )
      ls_reg.byte = inp( base_address + LSR_N );

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

   outp( base_address, out_char );

   return( 0 );
}

Функция exchange(), приведенная ниже, выполняет диалог с удаленным модемом. Символы, принимаемые через COM-порт от модема, отображаются на экране, а символы, набираемые на клавиатуре, передаются модему. Для передачи модему данных используется функция com_out() из модуля TO_MODEM.C, а для приема - функция from_modem() из модуля FROM_MDM.C.

// EXCHANGE.C

// функция exchange выполняет диалог с удаленным модемом
// символы, принимаемые через COM-порт от модема, отображаются
// на экране; символы, набираемые на клавиатуре, передаются
// модему также через COM-порт

#include <conio.h>

void        exchange( unsigned com_adr );
void        disp( char, char );
int         com_out( unsigned, char );


void exchange( unsigned com_adr ) {
   int flag = 1;

   while(flag) {

      char ch_in;
      unsigned char key;
      unsigned i,j;

      // если пользователь нажал на клавишу, получаем код
      // нажатого символа и передаем его модему

      if( kbhit() ) {
         key =  getch();

         // по нажатию клавиши Esc выходим из данной функции

         if( key == 27 ) {
            flag = 0;
            break;
         }

         // если пользователь нажал Enter, передаем
         // символ перевода строки и возврата каретки

         if( key == '\r' ) {

            // посылаем символ в COM-порт
            com_out( com_adr, 0xd );

            // отображаем символ на экране
            disp(0xd,7);

            // посылаем символ в COM-порт
            com_out( com_adr, 0xa );

            // отображаем символ на экране
            disp(0xa,7);
         }

         else {

            // отображаем символ на экране
            disp( key, // код символа
                  15   // его атрибут (интенсивно белый символ
                       // на черном фоне )
                );

            // посылаем символ в COM-порт
            com_out( com_adr, key );
         }
      }

      // если получены данные от модема, отображаем их на экране

      if( from_modem( com_adr, &ch_in ) == 0 )
         disp( ch_in,  // код символа
               2       // его атрибут (зеленый символ
                     // на черном фоне )
            );
   }
}

Модуль FROM_MDM.C содержит определение функции from_modem(), которая позволяет получить данные от модема. Это могут быть данные, принятые модемом от удаленного абонента, или ответ модема на переданную ему AT-команду.

Функция from_modem() работает следующим образом: считывает значение регистра состояния линии и проверяет бит D0. Если бит D0 равен нулю, значит, данные получены и готовы для чтения. В этом случае данные считываются через регистр данных и функция возвращает нулевое значение. Если бит D0 равен нулю, то нет данных для чтения и функция from_modem() возвращает значение -1:

// FROM_MDM.C

#include "uart_reg.h"

/**
*.Name         from_modem
*
*.Title        Получает от модема один символ.
*
*.Descr        Эта функция получает от модема через
*              COM-порт один символ.
*
*.Proto        int from_modem( unsigned base_address, char *in_char )
*
*.Params       unsigned base_address - базовый адрес асинхронного адаптера,
*              на котором находится модем;
*
*              char *in_char - символ, получаемый от модема,
*
*.Return       -1 - если нет данных от модема
*              0 - если данные считаны
**/

int from_modem( unsigned base_address, char *in_char ) {

   unsigned ls_reg;
   char temp;
   int   ret_num;

   temp = 0;
   ret_num = -1;

   ls_reg = base_address + LSR_N;

   _asm {

      cli

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

      mov   dx, ls_reg
      in    al,dx
      nop
      nop
      nop
      test  al,1

      // если данных нет, возвращаем -1

      jz    no_data

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

      mov   dx,base_address
      in    al,dx
      nop
      nop
      nop
      mov   temp,al

      // возвращаем 0

      mov   ret_num,0

no_data:

      sti
   }

   *in_char = temp;

   return( ret_num );
}

Модуль DISP.C является вспомогательным и определяет функцию disp(), используемую для вывода символов на экран непосредственно через видеопамять. Непосредственный вывод в видеопамять использован нами потому, что функции putch() и printf() работают слишком медленно. На больших скоростях модем может передать в COM-порт несколько новых символов, в то время как функция putch() еще не вывела ни одного.

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

Мы рассмотрим коммуникационную программу, использующую прерывания от COM-порта в следующей главе, а теперь приведем модуль DISP.C:

// DISP.C

// сегментный адрес видеопамяти
static unsigned  video_adr = 0xB800;

// текущее положение
static   int cur = 0;

// номер текущей строки * 160
static   int line = 0;


// функция disp() используется для вывода символов на экран
// непосредственно через видеопамять;
// подразумевается, что видеоадаптер находится в цветном
// текстовом режиме с разрешением 25*80 символов;
// вывод производится в нулевую страницу видеопамяти

void disp( char outchar,   // ASCII-код символа
           char attr       // атрибут символа
         ) {

   static char save_ch = 0, save_attr = 7;

   _asm {

      push  es

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

      mov   bx,cur
      add   bx,line

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

      mov   ax,video_adr
      mov   es,ax

      // восстанавливаем символ в позиции курсора
      // эмулируем курсор символом '_'

      mov   al,save_ch
      mov   es:[bx], al
      inc   bx

      mov   al,save_attr
      mov   es:[bx], al

   display:

      // проверяем управляющие символы CR и LF
      cmp   outchar, 20h
      jb    handl

      dec   bx

      // если весь экран заполнен, очищаем его и перемещаемся в
      // верхний левый угол экрана
      cmp   bx,3840
      jb    send_it

      // очищаем экран

      mov   cx,4000
      xor   bx,bx

   clr_scr:

      mov   es:[bx], 0
      inc   bx
      loop  clr_scr

      mov   line,0
      xor   bx,bx

      // записываем символ и его атрибут в текущей позиции экрана

   send_it:
      mov   al,BYTE PTR outchar
      mov   es:[bx], al
      inc   cur
      inc   bx
      mov   al,BYTE PTR attr
      mov   es:[bx], al
      inc   cur

      jmp   end_disp

      // обрабатываем управляющие символы CR, LF, Backspace

   handl:
      cmp   outchar, 0xd
      jne   next_1
      add   line,160
      jmp   end_disp

   next_1:

      cmp   outchar, 0xa
      jne   next_2
      mov   cur,0
      jmp   end_disp

   next_2:
      cmp   outchar, 0x8
      jne   next_3
      dec   cur
      dec   cur
      jmp   end_disp

   next_3:

   end_disp:

      // устанавливаем курсор в новую позицию

      mov   bx,cur
      add   bx,line

      mov   al,es:[bx]
      mov   save_ch,al

      mov   al,5fh
      mov   es:[bx], al
      inc   bx

      mov   al,es:[bx]
      mov   save_attr,al

      mov   al,7
      mov   es:[bx], al

      pop   es
   }
}


void disp_string( char *str, char attr ) {
   int i;

   for( i = 0; str[i]; i++ )
      disp( str[i], attr );
}

Вспомогательный модуль TIMER.C содержит определения функций sleep() и delay(). Эти функции используются в программе для организации временных задержек, в частности при передаче модему Escape-последовательности "+++" для перевода его в командный режим.

// TIMER.C
// определены функции sleep и delay, выполняющие временные задержки

#include <time.h>
#include <sys/timeb.h>

#include "timer.h"


/**
*.Name         sleep
*
*.Title        Приостанавливает выполнение программы
*
*.Descr        Эта функция приостанавливает выполнение
*              программы на заданное число секунд.
*
*.Proto        void sleep(time_t interval)
*
*.Params       time_t interval - время задержки в секундах
*
*.Return       не используется
*
*.Sample       timer.c
**/

void sleep(time_t interval) {

   time_t start;

   start = time((time_t *)NULL);

   // ожидаем, пока пройдет time_t секунд

   while ((time((time_t *)NULL) - start) < interval)
      delay(1000);
}


/**
*.Name         delay
*
*.Title        Приостанавливает выполнение программы
*
*.Descr        Эта функция приостанавливает выполнение
*              программы на заданное число миллисекунд.
*
*.Proto        void delay(int milliseconds)
*
*.Params       time_t interval - время задержки в миллисекундах
*
*.Return       не используется
*
*.Sample       timer.c
**/

void   delay   (int milliseconds) {

   struct timeb t;
   time_t seconds;
   unsigned last;

   if (milliseconds == 0)
      return;

   // определяем текущее время

   ftime(&t);

   last = t.millitm;
   seconds = t.time;

   // ожидаем milliseconds миллисекунд

   while( milliseconds > 0) {

      int count;

      // задержка
      for ( count = 0; count < 2000; count ++);

      // определяем текущее время

      ftime(&t);

      if (t.time == seconds)
         milliseconds -= (t.millitm - last);

      else
         milliseconds -= 1000 * (int) (t.time - seconds) -
                   (last - t.millitm);

      last = t.millitm;
      seconds = t.time;
   }
}
[Назад] [Содеожание] [Дальше]