5.1. Основные принципы программирования
модемов
5.2. Простейшая программа работы с модемом
5.3. Коммуникационная программа
5.4. Коммуникационная программа, использующая
прерывания
5.5. Сигнальные лампы для внутреннего модема
В этой главе мы рассмотрим основные принципы программирования модемов и
приведем две коммуникационные программы.
Доступ к модему происходит через последовательный асинхронный порт. При этом
для передачи модему команд их необходимо просто записать в регистр данных
COM-порта, на котором находится модем. Ответ от модема также поступает через
последовательный порт. Передавая модему команды, его можно проинициализировать,
перевести в режим автоответа или заставить набрать номер.
Когда модем наберет номер удаленного абонента или когда модему в режиме
автоответа придет вызов, он попытается установить связь с удаленным модемом.
После установления связи модем передает компьютеру через COM-порт специальное
сообщение (см. главу "Система команд hayes-модемов") и переключится из
командного режима в режим передачи данных. После этого данные, передаваемые
модему, перестают восприниматься им как команды и сразу передаются по телефонной
линии на удаленный модем.
Итак, после установления связи с удаленным модемом, коммуникационная
программа может начинать обмен данными. Обмен данными так же, как и передача
команд, осуществляется через COM-порт. Затем при помощи специальной
Escape-последовательности можно переключить модем из режима передачи данных
обратно в командный режим и положить трубку (AT-команда ATH0), разорвав связь с
удаленным модемом.
Принципы обмена данными с внешними устройствами через COM-порт представлены в
главе "Программирование асинхронного адаптера".
Приведем последовательность действий для установления связи и обмена данными
через модем.
Проводим инициализацию COM-порта, к которому подключен модем. Для этого
программируем регистры микросхемы UART, задавая формат данных (число стоповых
битов, длину слова) и скорость обмена. Заметим, что модем будет проводить
соединение с удаленным модемом как раз на этой скорости. Чем скорость выше, тем,
естественно, быстрее будет происходить обмен с удаленным модемом. Однако при
увеличении скорости на плохих телефонных линиях сильно возрастает количество
ошибок.
Передавая модему AT-команды через COM-порт, производим его инициализацию. При
помощи AT-команд можно установить различные режимы работы модема - выбрать
протокол обмена (CCITT или Bell), установить набор диагностических сообщений
модема и т.д.
- Соединяемся с удаленным модемом
Передаем модему команду набора номера (ATD). В этом случае модем набирает
номер и пытается установить связь с удаленным модемом. Или передаем модему
команду ATS0 = 1 для перевода его в режим автоответа. После этой команды модем
ожидает звонка от удаленного модема, а когда он приходит, пытается установить с
ним связь (см. главу "Система команд hayes-модемов").
В зависимости от режима, в котором находится модем, он может передавать
компьютеру различные сообщения. Например, если модем производит вызов удаленного
модема (AT-команда ATD), то модем может выдать следующие сообщения:
CONNECT |
успешное соединение |
BUSY |
номер занят |
NO DIALTONE |
на линии отсутствует сигнал коммутатора |
NO ANSWER |
абонент не отвечает |
NO CARRIER |
неудачная попытка установить связь |
Когда приходит звонок, модем передает компьютеру сообщение RING, если регистр
модема S0 равен нулю. В этом случае для ответа на звонок надо послать модему
команду ATA. Если модем находится в режиме автоответа и регистр модема S0 не
равен нулю, то модем автоматически пытается ответить на звонок и может выдать
следующие сообщения:
CONNECT |
успешное соединение |
NO DIALTONE |
на линии отсутствует несущая частота от удаленного модема
|
NO CARRIER |
неудачная попытка установить связь. |
Если модем передал компьютеру сообщение CONNECT, значит, он успешно произвел
соединение и теперь работает в режиме передачи данных. Теперь все данные,
которые вы передадите модему через COM-порт, будут преобразованы модемом в
форму, пригодную для передачи по телефонным линиям, и переданы удаленному
модему. И наоборот, данные, принятые модемом по телефонной линии, переводятся в
цифровую форму и могут быть прочитаны через COM-порт, к которому подключен
модем.
Если модем передал компьютеру сообщения BUSY, NO DIALTONE, NO ANSWER, NO
CARRIER, значит, произвести соединение с удаленным модемом не удалось и надо
попытаться повторить соединение.
- Переключаем модем в командный режим
После окончания работы коммуникационная программа должна перевести модем в
командный режим и передать ему команду положить трубку (ATH0). Для перевода
модема в командный режим можно воспользоваться Escape-последовательностью
"+++". После того как модем перешел в командный режим, можно опять
передавать ему AT-команды.
- Сбрасываем сигналы на линиях DTR и RTS
Низкий уровень сигналов DTR и RTS сообщает модему, что компьютер не готов к
приему данных через COM-порт.
При работе с асинхронным последовательным адаптером (COM-портом) вы можете
использовать механизм прерываний. Глава "Программирование асинхронного
адаптера" содержит теоретические сведения по этому вопросу, а в главе
"Коммуникационная программа, использующая прерывания" содержится
исходный текст коммуникационной программы, использующей прерывания для работы с
COM-портом. Если ваша программа использует прерывания от COM-порта, она должна
содержать обработчик прерываний, а также программировать контроллер прерываний
для разрешения прерываний.
Так как передача и прием данных модемом представляют собой длительный
процесс, то применение прерываний от COM-порта позволяет использовать
процессорное время для других нужд.
Сейчас мы приведем, возможно, не очень полезную, но очень простую программу,
работающую с модемом. Эта программа может только передавать команды модему.
Принимать данные от модема и выполнять другие функции, присущие программам
работы с модемом, она не может.
Программа работает следующим образом: сначала устанавливает сигнал DTR,
который сообщает модему, что компьютер готов к обмену данными. Далее, не
дожидаясь от модема ответного сигнала DSR, начинает передавать команды модему.
После передачи всех символов команды модему передается байт с кодом возврата
каретки, при приеме которого он выполняет посланную ему команду.
Отметим, что чтение кода возврата этой программой не выполняется, поэтому
рано или поздно возникнет ошибка (переполнение буфера).
Приведем сначала исходный текст функций для управления линией DTR: #include <stdio.h>
/**
*.Name dtr_on
*
*.Title Устанавливает сигнал DTR и RTS.
*
*.Descr Эта функция устанавливает сигналы DTR и RTS
* для заданного COM-порта.
*
*.Proto void dtr_on( unsigned base_address );
*
*.Params unsigned base_address - базовый адрес асинхронного адаптера
*
*.Return не используется
*
*.Sample dtr.c
**/
void dtr_on( unsigned base_address ) {
unsigned char MCR;
// считываем значение регистра управления модемом
MCR = inp( base_address + 4 );
// устанавливаем сигналы DTR и RTS в активное состояние
MCR |= 3;
// записываем новое значение в регистр управления модемом
outp( base_address + 4, MCR );
}
/**
*.Name dtr_off
*
*.Title Сбрасывает сигналы DTR и RTS.
*
*.Descr Эта функция сбрасывает сигналы DTR и RTS
* для заданного COM-порта.
*
*.Proto void dtr_on( unsigned base_address );
*
*.Params unsigned base_address - базовый адрес асинхронного адаптера
*
*.Return не используется
*
*.Sample dtr.c
**/
void dtr_off( unsigned base_address ) {
unsigned char MCR;
// считываем значение регистра управления модемом
MCR = inp( base_address + 4 );
// сбрасываем сигналы DTR и RTS
MCR &= 0xFC;
// записываем новое значение в регистр управления модемом
outp( base_address + 4, MCR );
}
Теперь приведем текст функции to_modem(), обеспечивающей передачу команд
модему: #include "sysp_com.h"
// объявления функций
void com_out( unsigned, char );
void to_modem( unsigned, char* );
/**
*.Name to_modem
*
*.Title Передает модему строку, оканчивающуюся нулем.
*
*.Proto void to_modem( unsigned base_address, char *out_str );
*
*.Params unsigned base_address - базовый адрес асинхронного адаптера,
* на котором находится модем;
*
* char *out_str - массив символов, передаваемый модему,
* заканчивается нулем
*
*.Return не используется
*
**/
void to_modem( unsigned base_address, char *out_str ) {
int i;
// последовательно передаем модему каждый символ из строки
for( i = 0; out_str[i] != '\0'; i++ )
com_out( base_address, out_str[i] );
// заканчиваем передачу строки и посылаем модему код
// возврата каретки, вызывающий исполнение введенной команды
com_out( base_address, 13 );
}
/**
*.Name com_out
*
*.Title Передает модему один символ.
*
*.Descr Эта функция передает один символ.
*
*.Proto void com_out( unsigned base_address, char out_char );
*
*.Params unsigned base_address - базовый адрес асинхронного адаптера,
* на котором находится модем;
*
* char out_char - символ, передаваемый модему,
*
*.Return не используется
**/
void com_out( unsigned base_address, char out_char ) {
unsigned char LCR, next;
// ожидаем, пока освободится регистр передатчика
// и можно будет передать следующий байт
for( next = 0; next == 0; )
next = 0x20 & inp( base_address + 5 );
// записываем передаваемый байт в регистр данных
// для последующей его передачи модему
outp( base_address, out_char );
}
Теперь перейдем собственно к самой программе. Как вы можете видеть, она
содержит всего несколько строк на Си: //TEST.C
#include <conio.h>
unsigned com_address( int );
void to_modem( unsigned, char* );
void dtr_on( unsigned );
void main(void) {
unsigned com_adr;
if(( com_adr = com_address( 1 )) < 0 ) exit( com_adr );
// передаем модему сигнал DTR
dtr_on( com_adr );
// набираем телефонный номер
to_modem( com_adr, "AT DP 411 64 17" );
// ожидаем нажатия на любую клавишу
getch();
// сбрасываем сигнал DTR
dtr_off( com_adr );
}
Теперь мы приступим к самому интересному - приведем подробный алгоритм
коммуникационной программы, а затем - исходный текст такой программы.
Сначала мы рассмотрим вариант коммуникационной программы без использования
прерываний от асинхронного порта. Этот вариант несколько проще, так как нам не
надо создавать довольно нетривиальный обработчик для этого прерывания, а также
программировать контроллер прерываний.
Итак, приступим. Как мы сказали ранее, первым шагом при программировании
модема надо считать инициализацию 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;
}
}
Программа, представленная в предыдущей главе, имеет один большой недостаток:
она должна постоянно производить опрос регистра состояния линии, с тем чтобы
определить момент, когда от модема поступит очередной символ. В результате
становится трудной, а иногда невозможной обработка поступающих символов.
Например, если вы сразу отображаете символы, получаемые от COM-порта, на экране,
то при использовании для этого функции putch() отдельные символы могут быть
потеряны. Дело в том, что функция putch() работает слишком медленно и на
скоростях 2400 бод и выше модем может успеть передать в COM-порт несколько новых
символов, в то время как функция putch() еще не вывела на экран ни одного
символа. В этом случае происходит ошибка переполнения входного буфера микросхемы
UART (см. бит D2 регистра состояния линии).
Таким образом, имеет смысл организовать прием и передачу символов модему в
фоновом режиме, используя прерывания по окончании приема и передачи символа.
Если ваша коммуникационная программа будет использовать прерывания, можно
организовать буфер принимаемых и передаваемых данных. Обработчик прерываний
должен проанализировать причину прерывания и либо передать в COM-порт очередной
символ из буфера передатчика (если прерывание произошло в результате передачи
очередного символа), либо считать поступивший символ из регистра данных и
записать его в буфер приемника (если прерывание произошло в результате приема от
модема очередного символа).
В этом случае процесс обмена идет в фоновом режиме и процессор может спокойно
заниматься обработкой принимаемых и передаваемых символов. Если программе
понадобится передать данные модему, она может просто записать их в буфер
передатчика. Для приема данных она должна считать их из буфера приемника.
Принципы использования прерываний
Последовательный асинхронный адаптер можно запрограммировать таким образом,
что всякий раз, когда он примет или передаст очередной байт, будет выработано
соответствующее прерывание.
Прерывания могут вырабатываться асинхронным адаптером в следующих случаях:
- изменилось состояние линии приемника: произошло переполнение приемника,
произошла ошибка четности или синхронизации, линия перешла в состояние BREAK;
- данные приняты и доступны для чтения через регистр данных;
- регистр передатчика пуст;
- изменилось состояние модема: изменилось состояние линий CTS, RI, DCD, DSR.
Вы можете отдельно запрещать или разрешать эти прерывания. Для этого
необходимо установить соответствующие биты в регистре управления прерываниями.
Как приходит прерывание от COM-порта? Как мы указывали ранее, каждому
COM-порту соответствует, кроме базового адреса его регистров, линия IRQ (см.
главы "Последовательный асинхронный адаптер" и "COM-порт и номера
IRQ"):
COM-порт |
IRQ |
Номер прерывания |
COM1 |
IRQ4 |
INT 0Ch |
COM2 |
IRQ3 |
INT 0Bh |
COM3 |
IRQ4 |
INT 0Ch |
COM4 |
IRQ3 |
INT 0Bh |
Заметим, что в данной таблице представлен только один возможный вариант
соответствия номеру COM-порта линии IRQ. Некоторые платы асинхронных адаптеров и
некоторые внутренние модемы имеют отдельно перемычки для выбора номера COM-порта
(адреса базового регистра) и номера линии IRQ.
Что представляет из себя обработчик прерываний асинхронного адаптера? После
вызова обработчика прерываний он должен:
Разрешить обработку прерываний
Необходимо выполнить команду sti, для того чтобы разрешить обработку
прерываний с более высоким приоритетом, чем прерывание от асинхронного адаптера.
Определить причину прерывания
Для этого следует считать содержимое регистра идентификации прерываня.
Состояние битов D1 D2 определяют причину прерывания:
Биты D2 D1 |
Причина прерывания |
00 |
прерывание по линии состояния; |
01 |
буфер передатчика пуст; |
10 |
данные приняты; |
11 |
изменилось состояние модема. |
В зависимости от того, какое произошло прерывание, его надо соответствующим
образом обработать.
Произошло прерывание по линии состояния
Считать регистр состояния линии и конкретизировать причину прерывания (данное
прерывание сбрасывается после чтения регистра состояния линии). Если это
необходимо, подать основной программе сигнал о произошедшей ошибке с целью ее
устранения. Например, в случае определения на линии сигнала BREAK (удаленный
модем повесил трубку), надо попытаться возобновить связь.
Прерывание по принятию данных
Очередной символ принят, и его можно считать через регистр данных. Прерывание
сбрасывается после чтения регистра данных. Принятый байт необходимо записать в
приемный буфер программы, из которого впоследствии его прочитает основная
программа. Буфер приемника удобно организовать в виде очереди.
Буфер передатчика пуст
Прерывание происходит в случае, если буфер передатчика пуст и можно передать
COM-порту очередной символ. Можно организовать буфер передатчика программы, в
который программа будет записывать данные, предназначенные для передачи через
COM-порт. В этом случае, когда придет прерывание, надо считать очередной символ
из буфера передатчика программы и записать его в регистр данных. Прерывание
сбрасывается после записи очередного символа в регистр данных UART. Если нет
данных для передачи (программный буфер передатчика пуст), можно запретить это
прерывание через регистр управления прерываниями.
Изменилось состояние модема
Прерывание происходит при изменении состояния входных линий CTS, RI, DCD,
DSR. Состояние этих линий можно определить, считав регистр состояния модема. Это
прерывание используется для обнаружения звонка на телефонной линии. Прерывание
автоматически сбрасывается после чтения регистра состояния модема.
Считать регистр идентификации прерывания
Может случиться, что одновременно произойдет несколько прерываний. В этом
случае бит D0 регистра идентификации прерываний равен единице. Тогда перед
завершением обработки прерывания необходимо обработать следующее прерывание в
соответствии с состоянием битов D1, D2. Так следует поступать до тех пор, пока
не будут обработаны все прерывания (бит D0 не станет равен нулю).
Обработать конец прерывания
Передать контроллеру прерываний команду обработки конца прерывания. Для этого
посылается в порт с адресом 20h команда конца прерывания: mov al,20h
out 20h,al
Закончить обработку прерывания
Теперь можно закончить обработку прерывания, выполнив команду iret.
Итак, мы завершили рассмотрение обработчика прерываний. Теперь нам осталось
изучить примерный порядок работы коммуникационной программы с использованием
прерываний.
Установить обработчик прерываний
Необходимо установить обработчик прерываний, изменив соответствующий элемент
таблицы векторов прерываний. Адрес старого обработчика сохраняется в глобальных
переменных.
Инициализация COM-порта
Сначала надо перевести в неактивное состояние линии DTR и RTS. Затем сбросить
регистр состояния линии, регистр состояния модема и регистр данных.
После того как мы сбросили регистры UART, можно приступить к инициализации
COM-порта. Во время инициализации задается формат данных - длина слова,
количество стоповых битов, наличие контроля по четности и скорость обмена.
Последним шагом в инициализации регистров UART является установка регистра
управления прерываниями, в который записывается соответствующее значение.
Например, чтобы разрешить генерацию прерываний при приеме очередного символа,
надо записать значение 01h в регистр управления прерываниями: // устанавливаем регистр управления прерываниями
outp(port_adr+ICR, 1); // ICR - адрес регистра
// управления прерываниями
На этом этап инициализации регистров UART можно считать законченным. Теперь
COM-порт подготовлен для обмена через него данными с модемом, но модем пока еще
не будет воспринимать данные от компьютера. Чтобы перевести его в рабочее
состояние, надо передать ему сигналы DTR и RTS. В ответ на эти сигналы модем
должен вернуть компьютеру сигналы DSR и CTS.
Инициализация контроллера прерывний
Для того чтобы прерывания от асинхронного адаптера выполнялись, необходимо
разрешить прерывание по соответствующей линии IRQ через регистр маски прерываний
контроллера прерываний: // считываем состояние регистра маски прерываний
mov dx,21h
in dx,al
// разрешаем прерывания от порта COM1
and al,11101111b
// записываем новое значение в регистр маски прерываний
out dx,al
Инициализация модема и установление связи
После установки обработчика прерываний и инициализации регистров COM-порта и
контроллера прерываний можно передавать модему AT-команды и принимать от него
ответ на них. При этом данные можно считывать (записывать) из COM-порта через
буфер обработчика прерываний.
Обмен данными с удаленным модемом
Установив связь с удаленным модемом, оба модема переходят в режим обмена
данными. Теперь можно начинать передавать и принимать данные.
Передача и прием данных от удаленного модема осуществляются так же, как
передача модему команд и прием от него сообщениий.
Завершение программы
Для завершения коммуникационной программы, использующей прерывания,
необходимо сбросить сигналы DTR и RTS и запретить через контроллер прерываний
прерывания от COM-порта: // считываем состояние регистра маски прерываний
mov dx,21h
in dx,al
// запрещаем прерывания от порта COM1
or al,00010000b
// записываем новое значение в регистр маски прерываний
out dx,al
Затем нужно восстановить старый вектор обработчика прерываний.
Коммуникационная программа CHAT
В этой главе мы приведем исходный текст коммуникационной программы CHAT. В
отличие от программы CHAT_S данная программа использует для работы с асинхронным
адаптером прерывания.
При помощи этой программы можно связаться с удаленным модемом, передавать и
принимать от него данные в формате ASCII. Например, вы можете позвонить на
станцию BBS и прочитать почтовые сообщения. Передачу и прием файлов программа не
поддерживает, иначе пример занимал бы слишком много места.
Данная телекоммуникационная программа может работать в двух режимах -
активном, когда она сама производит вызов удаленного модема, и пассивном, когда
программа находится в режиме ожидания звонка от удаленного модема. Для работы
программы в активном режиме необходимо запустить ее с параметром "1",
для пассивного режима - "0".
Большинство параметров программы, таких, как AT-команды инициализации,
телефонный номер, скорость обмена и номер COM-порта, можно настроить через файл
конфигурации setup.cfg. Образец этого файла представлен ниже: // строка инициализации для режима активного вызова
Initialize ATS0=0Q0E0M1V1X4&C1&D2
// команда, которая переводит модем в командный режим и
// кладет трубку
Dropline \d\d+++\d\dATH0\n\r\d
// строка инициализации для режима ожидания звонка
AutoAnswer ATS0=1Q0E0M1V1X4&C1&D2
// префикс телефонного номера
DialPrefix \r\pATDP
// суффикс телефонного номера
DialSuffix
// телефонный номер
DialNumber 1135810
// номер COM-порта в формате COMn, где n - номер порта
Device COM3
// время, отведенное на установку связи с удаленным модемом
DialTimeout 30
TimeoutAnswer 30
// временная задержка между сиимволами при передаче
CharDelay 0
// время реакции модема на команды
ModemTimeout 3
// скорость обмена данными
Speed 2400
Программа состоит из следующих модулей: CHAT.C // главная процедура программы
MODEM.C // передача данных модему через COM-порт
TIMER.C // реализация временных задержек
CONF.C // чтение файла конфигурации
SEND_COMM.C // передача команд модему
TOOLS.C // набор функций для работы с модулем UART.ASM
UART.ASM // обработчик прерываний и процедуры низкого уровня
Теперь приведем сами исходные тексты программы. Основной модуль программы
называется CHAT.C. В зависимости от параметра программы этот модуль вызывает
функцию call() - режим вызова удаленного модема - или функцию answer() - режим
ответа на приходящие звонки. Обе функции, call() и answer(), выполняют все
действия по программированию COM-порта, контроллера прерываний и модема. Данные
функции определены в модуле MODEM.C.
После окончания связи вызывается функция shutdown(), которая опускает
телефонную трубку и отключает обработчик прерываний. // CHAT.C
// основной модуль коммуникационной программы
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include "mod_link.h"
#include "common.h"
#include "modem.h"
#include "timer.h"
#include "tools.h"
void hello(void) {
printf("неправильно задан параметр программы \n"
"mod_link n, где n = 1 вызов, n = 0 ответ\n");
exit(0);
}
// основная процедура
void main( int argc, char *argv[] ) {
// программа должна вызываться параметром 1 или 0
// 1 - это активный режим, когда программа сама вызывает
// удаленный модем
// 0 - программа находится в режиме автоответа на
// приходящий вызов
if( argc < 2 )
hello();
0 if( strcmp( argv[1], "0") && strcmp( argv[1], "1" ))
hello();
if( !strcmp( argv[1], "1" ) )
// если программа запущена с параметром "1", вызывается
// функция call() из модуля MODEM.C, выполняющая вызов
// удаленного модема
call();
else
// если программа запущена с параметром "0", вызывается
// функция answer(), переключающая модем в режим автоответа
answer();
// освобождаем телефон
shutdown();
}
Данный модуль определяет функции высокого уровня для работы с модемом:
- call() - режим вызова удаленного модема,
- answer() - режим ответа на приходящие звонки,
- shutdown() - завершение сеанса связи,
- exchange() - диалог пользователя и удаленного модема.
Эти функции вызывают модули более низкого уровня: SEND_COMM.C, TOOLS.C. // MODEM.C
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include "uart.h"
#include "common.h"
#include "modem.h"
#include "mod_link.h"
#include "timer.h"
#include "tools.h"
#include "conf.h"
char *device = "COM3"; // номер используемого порта в формате
// COMn, где n от 1 до 4
unsigned dialTimeout = 12, // продолжительность ожидания соединения
chardelay = 0, // задержка при передаче между символами
modemTimeout = 3, // таймаут на получение ответа от модема
answerTimeout; // ппродолжительность ожидания звонка
unsigned speed = 2400; // скорость обмена данными
char initialize[80]; // команда инициализации
char dropline[80]; // команда повесить трубку
char autoanswer[80]; // ответ на вызов в режиме автоответа
char dialPrefix[80]; // префикс номера
char dialSuffix[80]; // суффикс номера
char dialNumber[80]; // телефонный номер
static boolean getmodem( const char *brand);
static boolean sendlist( char **list, int timeout, int lasttimeout);
static boolean sendalt( char *string, int timeout);
void exchange( void );
// call
// вызов удаленного пользователя
int call() {
char str[80], buf[80];
char *exp;
int i,j;
// определяем параметры связи (считываем файл конфигурации)
getconfig();
// устанавливаем обработчик прерываний и инициализируем
// регистры UART и контроллера прерываний
if (openline(device, speed))
return FALSE;
// очищаем приемный буфер
while (sread(buf,1,0));
printf("инициализируем модем\n\n");
// передаем модему строку инициализации
// (строка инициализации определяется ключевым словом Initialize
// в файле конфигурации setup.cfg)
sendstr( initialize );
// ожидаем ответа модема
sleep(modemTimeout);
// считываем и отображаем на экране ответное сообщение модема
if( r_count_pending() > 0 ) {
sread(str, i = r_count_pending(), 0);
str[i] = '\0';
for( j = 0; j < i; j++ )
putch( str[j] );
}
// передаем модему команду набора номера
strcpy(buf, dialPrefix);
strcat(buf, dialNumber);
strcat(buf, dialSuffix);
printf( "набираем номер\n\n");
sendstr( buf );
printf( "ожидаем соединение\n\n");
// производим обмен данными с удаленным модемом,
// пока не нажата клавиша ESC
exchange();
return(0);
}
// answer
// отвечает на звонок
int answer( void ) {
char c;
// определяем параметры связи
getconfig();
// устанавливаем обработчик прерываний и инициализируем
// регистры UART и контроллера прерываний
if (openline(device, speed))
exit(-2);
// очищаем приемный буфер
while (sread(&c ,1,0));
printf("инициализируем модем\n\n");
// передаем модему строку инициализации
// (строка инициализации определяется ключевым словом Autoanswer
// в файле конфигурации setup.cfg)
sendstr( autoanswer );
sleep(modemTimeout);
printf("ожидаем звонок\n");
// производим обмен данными с удаленным модемом,
// пока не нажата клавиша ESC
exchange();
return(0);
}
// shutdown
// функция кладет телефонную трубку
void shutdown( void ) {
printf("\n\nсвязь окончена, освобождаем телефон\n");
// передаем команду положить трубку
sendstr( dropline );
// восстанавливаем старый обработчик прерываний
closeline();
}
// slowwrite
// передать символ модему с задержкой, определяемой ключевым
// словом CharDelay в файле конфигурации
void slowwrite( char *s, int len) {
swrite( s , len );
if (chardelay > 0) delay(chardelay);
}
// exchange
// функция выполняет диалог пользователя и удаленного модема
void exchange( void ) {
int flag = 1;
while(flag) {
unsigned char str[80];
unsigned char key;
unsigned i,j;
// если пользователь нажал на клавиатуру, получаем код
// нажатого символа и передаем его модему
if( kbhit() ) {
key = getch();
// по нажатию клавиши Esc выходим из программы
if( key == 27 ) {
ssendbrk( 3 );
flag = 0;
break;
}
if( key == '\r' ) putch( '\n' );
putch(key);
swrite( &key, 1);
}
// если получены данные от модема, отображаем их на экране
if( r_count_pending() > 0 ) {
delay(100);
sread(str, i = r_count_pending(), 0);
str[i] = '\0';
for( j = 0; j < i; j++ )
putch( str[j] );
}
}
}
Модуль CONF.C определяет функцию getconfig(), считывающую файл конфигурации
setup.cfg и записывающую считанные значения в глобальные переменные.
Конфигурационный файл может содержать следующие команды: Initialize [строка инициализации для режима активного вызова]
Dropline [команда для освобождения телефона]
AutoAnswer [строка инициализации для режима ожидания звонка]
DialPrefix [ префикс телефонного номера]
DialSuffix [суффикс телефонного номера]
DialNumber [телефонный номер]
Device [номер COM-порта]
DialTimeout [время, отведенное на установку связи с удаленным модемом]
TimeoutAnswer [время, отведенное на установку связи с удаленным модемом]
CharDelay [временная задержка между символами при передаче]
ModemTimeout [время реакции модема на команды]
Speed [скорость обмена данными]
Итак, исходный текст модуля CONF.C: // CONF.C
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "conf.h"
void Number( unsigned *num );
void Token( char *string );
FILE *CfgFile;
char LineBuf[256];
extern char initialize[80];
extern char dropline[80];
extern char autoanswer[80];
extern char dialPrefix[80];
extern char dialSuffix[80];
extern char dialNumber[80];
extern unsigned chardelay;
extern unsigned dialTimeout;
extern unsigned modemTimeout;
extern unsigned answerTimeout;
extern unsigned speed;
extern char *device;
/**
*.Name getconfig
*
*.Title Считывает файл setup.cfg.
*
*.Descr Эта функция считывает данные из файла setup.cfg
*
*.Proto void getconfig(void)
*
*.Params не используется
*
*.Return не используется
**/
void getconfig(void) {
CfgFile=fopen("setup.cfg","r");
if(CfgFile == NULL) {
cprintf("\r\nОтсутствует файл SETUP.CFG.");
exit(-1);
}
// заполняем глобальные переменные
for(;;) {
fgets(LineBuf,255,CfgFile);
if(feof(CfgFile)) break;
STRIP(LineBuf);
if(OPERATOR("Dropline")) Token( dropline );
else if(OPERATOR("Initialize")) Token( initialize );
else if(OPERATOR("AutoAnswerr")) Token( autoanswer );
else if(OPERATOR("DialNumber")) Token( dialNumber );
else if(OPERATOR("DialPrefix")) Token( dialPrefix );
else if(OPERATOR("DialSuffix")) Token( dialSuffix );
else if(OPERATOR("Device")) Token( device );
else if(OPERATOR("CharDelay")) Number( &chardelay );
else if(OPERATOR("DialTimeout")) Number( &dialTimeout );
else if(OPERATOR("ModemTimeout")) Number( &modemTimeout );
else if(OPERATOR("TimeoutAnswer")) Number( &answerTimeout );
else if(OPERATOR("Speed")) Number( &speed );
}
fclose(CfgFile);
}
void Token( char *string ) {
char *next;
next = strcpy( string, strchr( LineBuf, ' ' ) + 1 );
if(next == NULL) string[0] = '\0';
}
void Number( unsigned *num ) {
char buf[80];
strcpy( buf, strchr( LineBuf, ' ' ) + 1 );
*num = atoi( buf );
}
Следующий модуль - SEND_COMM.C определяет функцию sendstr(), которая
используется для передачи модему AT-команд. // SEND_COMM.C
#include <time.h>
#include "common.h"
#include "modem.h"
#include "get_word.h"
#include "timer.h"
#include "tools.h"
#include "total.h"
// writestr
// обработка управляющих символов
static unsigned writestr(register char *s) {
register char last = '\0';
int no_CR = FALSE;
unsigned char digit;
while (*s) {
if (last == '\\') {
last = *s;
switch (*s) {
// задержка на две секунды
case 'd':
case 'D':
sleep(2);
break;
// не передавать символ возврата каретки
// в конце строки
case 'c':
case 'C':
no_CR = TRUE;
break;
// передать символ возврата каретки
case 'r':
case 'R':
slowwrite("\r", 1);
break;
// передать символ возврата каретки
case 'n':
case 'N':
slowwrite("\n", 1);
break;
// задержка 500 миллисекунд
case 'p':
case 'P':
delay(500);
break;
default:
slowwrite(s, 1);
last = '\0';
}
}
else if (*s != '\\')
slowwrite(s, 1);
else
last = *s;
s++;
}
return no_CR;
}
// sendstr
// послать строку в буфер передатчика,
// при этом обрабатываются следующие управляющие символы:
// d (D) - задержка на две секунды
// c (C) - не передавать символ возврата каретки в конце строки
// r (R) - передать символ возврата каретки
// n (N) - передать символ перевода строки
// p (P) - задержка 500 миллисекунд
void sendstr(char *str) {
if(!equal(str,"")) {
if(!writestr(str))
slowwrite("\r", 1);
}
else
slowwrite("\r", 1);
return;
}
Модуль TOOLS.C содержит определения функций для работы с модулем UART.ASM. // TOOLS.C
//
#include <assert.h>
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "common.h"
#include "tools.h"
#include "uart.h"
#include "timer.h"
unsigned port_active = FALSE;
static unsigned current_baud;
#define STOPBIT 1
static unsigned hangup_needed = TRUE;
/**
*.Name openline
*
*.Title Устанавливает текущий COM-порт.
*
*.Descr Эта функция устанавливает текущий асинхронный
* порт, с которым в дальнейшем будет происходить обмен.
*
*.Proto int openline(char *name, unsigned baud)
*
*.Params char *name - номер COM-порта
* unsigned baud - скорость обмена данными
*
*.Return не используется
**/
int openline(char *name, unsigned baud) {
int value;
// если порт уже активен, закрываем его
if (port_active)
closeline();
if (sscanf(name, "COM%d", &value) != 1) {
exit(-1);
}
// выбираем текущий COM-порт
select_port(value);
// сохраняем адрес старого обработчика прерываний COM-порта
save_com();
// устанавливаем новый обработчик прерываний COM-порта
install_com();
// программируем текущий COM-порт
// скорость, связь через модем, не проверяем четность
// один стоповый бит
open_com(baud, 'M', 'N', STOPBIT);
// запоминаем скорость
current_baud = baud;
// устанавливаем линию DTR в активное состояние
// (компьютер готов к обмену данными)
dtr_on();
port_active = TRUE;
return( 0 );
}
/**
*.Name sread
*
*.Title Читает символы из COM-порта.
*
*.Descr Эта функция читает заданное число символов
* из буфера приемника асинхронного порта.
*
*.Proto unsigned sread(char *buffer, unsigned wanted, unsigned timeout)
*
*.Params char *buffer - указатель на буфер в который будут записаны символы
* из буфера приемника
* unsigned wanted - число символов, которое надо прочитать
* из буфера приемника
* unsigned timeout - время, отведенное на чтение символов
*
*.Return количество символов, прочитанных из буфера приемника
**/
unsigned sread(char *buffer, unsigned wanted, unsigned timeout) {
time_t start;
hangup_needed = TRUE;
// определяем начальное время
start = time(nil(time_t));
for(;;) {
unsigned int pending;
// определяем число символов в буфере приемника
pending = r_count_pending();
// если в буфере есть необходимое количество символов
if (pending >= wanted) {
unsigned int i;
// считываем из буфера нужное число символов
for (i = 0; i < wanted; i++)
*buffer++ = (char) receive_com();
return pending;
}
// если в буфере приемника меньше символов, чем заказано
// для чтения, проверяем, не иcтекло ли отведенное для чтения
// время
else {
time_t now = time(nil(time_t));
time_t elapsed = now - start;
delay(0);
if (elapsed >= (long) timeout)
return pending;
}
}
}
/**
*.Name swrite
*
*.Title Запись символов в COM-порт.
*
*.Descr Эта функция записывает заданное число символов
* в буфер передатчика асинхронного порта.
*
*.Proto int swrite(char *data, unsigned len)
*
*.Params char *data - указатель на буфер данных
* unsigned len - число символов, которое надо записать
* в буфер передатчика
*
*.Return количество символов, записанных в буфер передатчика
*
*.Sample comlib.c
**/
int swrite(char *data, unsigned int len) {
unsigned int i;
hangup_needed = TRUE;
// записываем входные данные в буфер передатчика
// асинхронного порта
for (i = 0; i < len; i++)
send_com(*data++);
return len;
}
/**
*.Name ssendbrk
*
*.Title Передает сигнал BREAK удаленному модему.
*
*.Proto void ssendbrk()
*
*.Params Не используются.
*
*.Return Не используется.
*
*.Sample comlib.c
**/
void ssendbrk(void) {
break_com();
}
/**
*.Name closeline
*
*.Title Закрывает COM-порт.
*
*.Descr Эта функция восстанавливает старые значения
* векторов прерываний и запрещает прерывания
* от COM-порта.
*
*.Proto void closeline(void)
*
*.Params Не используются.
*
*.Return Не используется.
*
*.Sample comlib.c
**/
void closeline(void) {
int far *stats;
port_active = FALSE;
// отменяем сигнал DTR
dtr_off();
// запрещаем прерывания от COM-порта
close_com();
// восстанавливаем векторы прерываний
restore_com();
}
Вспомогательный модуль TIMER.C содержит определения функций sleep() и
delay(). Эти функции используются в программе для организации временных
задержек. // 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;
}
}
Модуль UART.ASM - это основной модуль программы CHAT. Он содержит обработчик
прерываний от COM-порта и функции низкого уровня для работы с ним.
Обработчик прерываний имеет два буфера - буфер приемника и буфер передатчика.
Через эти буферы осуществляется обмен данными между программой и обработчиком
прерываний. Буферы выполнены в виде очереди. ;
; UART.ASM
; модуль управления модемом и COM-портом нижнего уровня
;
; определяем размеры буфера приемника и передатчика
R_SIZE EQU 2048 ; размер приемного буфера
S_SIZE EQU 500 ; размер буфера передатчика
; номера обработчиков прерываний
INT_COM1 EQU 0Ch ; COM1
INT_COM2 EQU 0Bh ; COM2
INT_COM3 EQU 0Ch ; COM3
INT_COM4 EQU 0Bh ; COM4
; порты контроллера прерываний 8259
OCR EQU 20H ; управляющий регистр 8259
IMR EQU 21H ; регистр маски прерываний 8259
; константы для управления контроллером прерываний
E_IRQ4 EQU 00010000B
D_IRQ4 EQU 11101111B
EOI4 EQU 01100100B
E_IRQ3 EQU 00001000B
D_IRQ3 EQU 11110111B
EOI3 EQU 01100011B
;
; область переменных BIOS
;
; адреса базовых регистров последовательных асинхронных адаптеров
BIOS_VAR SEGMENT AT 40H
rs232_base DW 4 DUP(?)
BIOS_VAR ENDS
;
; таблица для каждого COM-порта
;
SP_TAB STRUC
port DB ? ; 1, 2, 3 или 4
; параметры для этого уровня прерываний
int_com DB ? ; номер прерывания
e_irq DB ?
d_irq DB ?
eoi DB ?
; обработчики прерываний для этого уровня
int_hndlr DW ? ; смещение обработчика прерываний
old_com_off DW ? ; смещение старого обработчика прерываний
old_com_seg DW ? ; сегмент старого обработчика прерываний
; параметры COM-порта
installed DB ? ; установлен ли порт на этом компьютере? (1=да,0=нет)
baud_rate DW ?
device_conn DB ? ; M(Модем), D(Нуль-модем)
parity DB ? ; N(ONE), O(DD), E(VEN), S(PACE), M(ARK)
stop_bits DB ? ; 1, 2
; счетчики ошибок
error_block DW 8 DUP(?)
; порты 8250
DATREG DW ? ; регистр данных
IER DW ? ; регистр управления прерываниями
IIR DW ? ; регистр идентификации прерывания
LCR DW ? ; регистр управления линией
MCR DW ? ; регистр управления модемом
LSR DW ? ; регистр состояния линии
MSR DW ? ; регистр состояния модема
DLL EQU DATREG ; младший регистр делителя
DLH EQU IER ; старший регистр делителя
; указатели буферов FIFO
; индекс первого символа в буфере передатчика
start_s_data DW ?
; индекс первого свободного элемента буфера передатчика
end_s_data DW ?
; индекс первого символа в буфере приемника
start_r_data DW ?
; индекс первого свободного элемента буфера приемника
end_r_data DW ?
; счетчики количества символов в буферах
size_s_data DW ? ; число символов в буфере передатчика
size_r_data DW ? ; число символов в буфере приемника
; буфера
send_buf DB S_SIZE DUP(?) ; буфер передатчика
reciave_buf DB R_SIZE DUP(?) ; буфер приемника
SP_TAB ENDS
EFRAME EQU error_block+6 ; ошибка синхронизации
EPARITY EQU error_block+8 ; ошибка четности
EOVFLOW EQU error_block ; произошло переполнение буфера
EDSR EQU error_block+12 ; модем не ответил сигналом DSR
EOVRUN EQU error_block+2 ; ошибка переполнения
EBREAK EQU error_block+4 ; обнаружен запрос на прерывание
EXMIT EQU error_block+10 ; ошибка при передаче
ECTS EQU error_block+14 ; модем не ответил сигналом CTS
;
;
DGROUP GROUP _DATA
_DATA SEGMENT public 'DATA'
DIV50 DW 2304
; текущий номер области данных порта
CURRENT_AREA DW AREA1
; область данных для каждого порта
AREA1 SP_TAB <1,INT_COM1,E_IRQ4,D_IRQ4,EOI4> ; область данных COM1
AREA2 SP_TAB <2,INT_COM2,E_IRQ3,D_IRQ3,EOI3> ; область данных COM2
AREA3 SP_TAB <3,INT_COM3,E_IRQ4,D_IRQ4,EOI4> ; область данных COM3
AREA4 SP_TAB <4,INT_COM4,E_IRQ3,D_IRQ3,EOI3> ; область данных COM4
_DATA ENDS
COM_TEXT SEGMENT PARA public 'CODE'
ASSUME cs:COM_TEXT,ds:DGROUP,es:NOTHING
public _select_port
public _save_com
public _install_com
public _restore_com
public _open_com
public _close_com
public _dtr_on
public _dtr_off
public _r_count
public _s_count
public _receive_com
public _send_com
public _break_com
public _com_errors
;
; выбор активного порта
; [bp+6] - номер порта
_select_port PROC FAR
push bp
mov bp, sp
mov ax, [bp+6] ;получаем в ax аргумент функции
cmp al,1 ; установлен порт 1?
je port1 ; да
cmp al,2 ; установлен порт 2?
je port2 ; да
cmp al,3 ; установлен порт 3?
je port3 ; да
cmp al,4 ; установлен порт 4?
je port4 ; да
jmp set_carrent_area
port1:
mov ax,OFFSET DGROUP:AREA1 ; выбираем область данных COM1
jmp short set_carrent_area
port2:
mov ax,OFFSET DGROUP:AREA2 ; выбираем область данных COM2
jmp short set_carrent_area
port3:
mov ax,OFFSET DGROUP:AREA3 ; выбираем область данных COM3
jmp short set_carrent_area
port4:
mov ax,OFFSET DGROUP:AREA4 ; выбираем область данных COM4
set_carrent_area:
; записываем в переменной CURRENT_AREA смещение
; текущей области данных
mov CURRENT_AREA,ax
mov sp,bp
pop bp
ret
_select_port ENDP
;
; сохранение текущего вектора прерывания COM-порта
;
_save_com PROC FAR
push bp
mov bp,sp
push si
; записываем в si указатель на текущую область данных
mov si,CURRENT_AREA
push es
mov AREA1.int_hndlr,OFFSET int_hndlr1
mov AREA2.int_hndlr,OFFSET int_hndlr2
mov AREA3.int_hndlr,OFFSET int_hndlr3
mov AREA4.int_hndlr,OFFSET int_hndlr4
; сохраняем старый вектор прерывания
mov ah,35H
mov al,int_com[si] ; номер прерывания
int 21h
; записываем в переменные old_com_off и old_com_seg
; соответственно сегмент и смещение старого вектора прерывания
mov old_com_off[si],bx
mov bx,es
mov old_com_seg[si],bx
pop es
pop si
mov sp,bp
pop bp
ret
_save_com ENDP
;
; install_com: установить активный порт
;
; возвращает в регистре ax - 1 при успешной установке
; и 0 в случае ошибки
;
_install_com PROC FAR
push bp
mov bp,sp
push si
mov si,CURRENT_AREA
push es
cmp installed[si],1
jne go_install
jmp alredy_ok
; очищаем счетчики ошибок
go_install:
mov WORD PTR EOVFLOW[si],0 ; переполнение буфера передатчика
mov WORD PTR EOVRUN[si],0 ; ошибка переполнения при приеме
mov WORD PTR EBREAK[si],0 ; обнаружен запрос на прерывание
mov WORD PTR EFRAME[si],0 ; ошибка синхронизации
mov WORD PTR EPARITY[si],0 ; ошибка четности
mov WORD PTR EXMIT[si],0 ; ошибка при передаче
mov WORD PTR EDSR[si],0 ; не получен сигнал DSR
mov WORD PTR ECTS[si],0 ; не получен сигнал CTS
; определяем базовый адрес используемого COM порта
mov bx,BIOS_VAR
mov es,bx
ASSUME es:BIOS_VAR
cmp port[si],1 ; порт 1?
je adr_3F8
cmp port[si],2 ; порт 2?
je adr_2F8
cmp port[si],3 ; порт 3?
je adr_3E8
cmp port[si],4 ; порт 4?
je adr_2E8
int 20H
adr_3F8:
mov ax,3F8H
jmp cmp_bios
adr_2F8:
mov ax,2F8H
jmp cmp_bios
adr_3E8:
cmp rs232_base+4,0
je adr_3E8_A
mov ax,rs232_base+4
jmp cmp_bios
adr_3E8_A:
mov ax,3E8H
mov rs232_base+4,ax
jmp cmp_bios
adr_2E8:
cmp rs232_base+6,0
je adr_2E8_A
mov ax,rs232_base+6
jmp cmp_bios
adr_2E8_A:
mov ax,2E8H
mov rs232_base+6,ax
; проверяем, определена ли соответствующая переменная
; BIOS
cmp_bios:
cmp ax,rs232_base
je set_reg_adr
cmp ax,rs232_base+2
je set_reg_adr
cmp ax,rs232_base+4
je set_reg_adr
cmp ax,rs232_base+6
jne bad_exit
set_reg_adr:
mov bx,DATREG
mov cx,7
set_next_reg_adr:
mov WORD PTR [si][bx],ax
inc ax
add bx,2
loop set_next_reg_adr
; устанавливаем вектор прерывания на наш обработчик
mov AREA1.int_hndlr,OFFSET int_hndlr1
mov AREA2.int_hndlr,OFFSET int_hndlr2
mov AREA3.int_hndlr,OFFSET int_hndlr3
mov AREA4.int_hndlr,OFFSET int_hndlr4
mov ah,25H
mov al,int_com[si] ; номер прерывания
mov dx,OFFSET DGROUP:int_hndlr[si]
push ds
push cs
pop ds
int 21h
pop ds
; поднимаем флаг - порт установлен
alredy_ok:
mov installed[si],1
pop es
; возвращаем 1
mov ax,1
pop si
mov sp,bp
pop bp
ret
; порт не установлен
bad_exit:
mov installed[si],0
pop es
; возвращаем 0
mov ax,0
pop si
mov sp,bp
pop bp
ret
_install_com ENDP
;
; восстановление векторов прерываний
;
_restore_com PROC FAR
push bp
mov bp,sp
push si
; отмечаем COM-порт как неактивный
mov si,CURRENT_AREA
mov installed[si],0
; восстанавливаем вектор прерывания
mov ah,25H
mov al,int_com[si]
mov dx,old_com_off[si]
mov bx,old_com_seg[si]
push ds
mov ds,bx
int 21h
pop ds
pop si
mov sp,bp
pop bp
ret
_restore_com ENDP
;
; открыть COM порт
;
; сброс буферов передатчика и приемника,
; инициализация регистров UART 8250
; разрешение прерываний от UART 8250
; (программирование контроллера прерываний)
;
; [bp+6] = скорость обмена
; [bp+8] = способ соединения - M(Модем), D(Нуль-модем)
; [bp+10] = четность - N(ONE), O(DD), E(VEN), S(PACE), M(ARK)
; [bp+12] = число стоповых бит 1, 2
;
_open_com PROC FAR
push bp
mov bp,sp
push si
mov si,CURRENT_AREA
; запрещаем прерывания
cli
mov ax,[bp+6]
mov baud_rate[si],ax
mov bh,[bp+8]
mov device_conn[si],bh
mov bl,[bp+10]
mov parity[si],bl
mov ch,[bp+12]
mov stop_bits[si],CH
; сбрасываем буферы и указатели
mov start_s_data[si],0
mov end_s_data[si],0
mov start_r_data[si],0
mov end_r_data[si],0
mov size_s_data[si],0
mov size_r_data[si],0
; проверяем, установлен ли уже обработчик прерываний
test installed[si],1
jnz reset_uart
jmp exit_open
reset_uart:
; устанавливаем регистры UART 8250
; сбрасываем регистр управления модемом
mov al,0
mov dx,MCR[si]
out dx,al
jmp $+2
; сбрасываем регистр состояния линии
mov dx,LSR[si]
in al,dx
jmp $+2
; сбрасываем регистр данных
mov dx,DATREG[si]
in al,dx
jmp $+2
; сбрасываем регистр состояния модема
mov dx,MSR[si]
in al,dx
; определяем делитель частоты тактового генератора
mov ax,50
mul DIV50
div baud_rate[si]
mov bx,ax
; переключаем регистр данных и регистр управления прерываниями
; для ввода делителя частоты тактового генератора
mov dx,LCR[si]
mov al,80H
out dx,al
jmp $+2
; вводим младший байт делителя частоты тактового генератора
mov dx,WORD PTR DLL[si]
mov al,bl
out dx,al
jmp $+2
; вводим старший байт делителя частоты тактового генератора
mov dx,WORD PTR DLH[si]
mov al,bh
out dx,al
jmp $+2
; определяем четность и число стоповых битов
mov al,03H
cmp parity[si],'O'
jne next1
mov al,0ah
jmp short next3
next1:
cmp parity[si],'E'
jne next2
mov al,1ah
jmp short next3
next2:
cmp parity[si],'M'
jne next3
mov al,2ah
next3:
test stop_bits[si],2
jz stop1
or al,4
stop1:
mov dx,LCR[si]
out dx,al
; разрешаем прерывания для 8259 и 8250
; устанавливаем регистр маски прерываний так, чтобы
; разрешить прерывания от асинхронного порта
in al,IMR
and al,d_irq[si]
out IMR,al
; разрешаем генерацию прерываний при готовности принимаемых
; данных, по состоянию BREAK и по ошибке
mov dx,IER[si]
mov al,5
out dx,al
jmp $+2
; устанавливаем DTR, RTS, OUT2
mov dx,MCR[si]
mov al,0bh
out dx,al
exit_open:
sti
pop si
mov sp,bp
pop bp
ret
_open_com ENDP
;
; запрещаем прерывания от асинхронного порта
;
_close_com PROC FAR
push bp
mov bp,sp
push si
mov si,CURRENT_AREA
test installed[si],1
jz exit_close
; запрещаем прерывания UART 8250
mov dx,IER[si]
mov al,0
out dx,al
; маскируем прерывания от UART
mov dx,IMR
in al,dx
or al,e_irq[si]
jmp $+2
out dx,al
exit_close:
pop si
mov sp,bp
pop bp
ret
_close_com ENDP
;
; снимаем сигнал DTR
;
_dtr_off PROC FAR
push bp
mov bp,sp
push si
pushf
push ax
push dx
push si
mov si,CURRENT_AREA
test installed[si],1
jz exit_dtr_off
; устанавливаем регистр управления модемом,
; сбрасываем сигналы DTR и RTS
mov dx,MCR[si]
mov al,08H
out dx,al
exit_dtr_off:
pop si
pop dx
pop ax
popf
pop si
mov sp,bp
pop bp
ret
_dtr_off ENDP
;
; устанавливаем сигнал DTR
;
_dtr_on PROC FAR
push bp
mov bp,sp
push si
pushf
push ax
push dx
push si
mov si,CURRENT_AREA
test installed[si],1
jz exit_dtr_on
; устанавливаем регистр управления модемом,
; устанавливаем сигналы DTR, RTS, OUT2
mov dx,MCR[si]
mov al,0bh
out dx,al
exit_dtr_on:
pop si
pop dx
pop ax
popf
pop si
mov sp,bp
pop bp
ret
_dtr_on ENDP
;
; возвращаем в регистре ax число байт в регистре приемника,
; а в регистре dx общий размер буфера приемника
;
_r_count PROC FAR
push bp
mov bp,sp
push si
pushf
push si
mov si,CURRENT_AREA
mov ax,0
mov dx,R_SIZE
test installed[si],1
jz exit_r_count
; записываем в регистр ax число символов в буфере приемника
mov ax,size_r_data[si]
exit_r_count:
pop si
popf
pop si
mov sp,bp
pop bp
ret
_r_count ENDP
;
; получаем очередной символ из буфера приемника,
; полученный символ удаляется из буфера
;
_receive_com PROC FAR
push bp
mov bp,sp
push si
pushf
push bx
push si
mov si,CURRENT_AREA
mov ax,-1
test installed[si],1
jz exit_receive_com
; возвращаемся, если буфер приемника пуст
cmp size_r_data[si],0
je exit_receive_com
mov ah,0
mov bx,start_r_data[si]
mov al,reciave_buf[si][bx]
cmp parity[si],'N'
je no_parity
; если производится поверка на четность, то маскируем старший бит
and al,7FH
no_parity:
inc bx
cmp bx,R_SIZE
jb rec_ptr_no_max
mov bx,0
rec_ptr_no_max:
mov start_r_data[si],bx
dec size_r_data[si]
exit_receive_com:
pop si
pop bx
popf
pop si
mov sp,bp
pop bp
ret
_receive_com ENDP
;
; функция возвращает в регистре ax число свободных байт в
; буфере передатчика, а в регистре dx общий размер буфера передатчика
;
_s_count PROC FAR
push bp
mov bp,sp
push si
pushf
push si
mov si,CURRENT_AREA
mov ax,0
mov dx,S_SIZE
test installed[si],1
jz exit_s_count
mov ax,S_SIZE
sub ax,size_s_data[si]
exit_s_count:
pop si
popf
pop si
mov sp,bp
pop bp
ret
_s_count ENDP
;
; поместить символ в буфер передатчика
; [bp+6] - символ
;
_send_com PROC FAR
push bp
mov bp,sp
push si
mov al,[bp+6]
pushf
push ax
push bx
push dx
push si
mov si,CURRENT_AREA
test installed[si],1
jz exit_send_com
cmp size_s_data[si],S_SIZE
jl no_s_EOVFLOW
; произошло переполнение буфера передатчика
inc WORD PTR EOVFLOW[si]
jmp short exit_send_com
no_s_EOVFLOW:
mov bx,end_s_data[si]
mov send_buf[si][bx],al
inc bx
cmp bx,S_SIZE
jl no_send_ptr_max
mov bx,0
no_send_ptr_max:
mov end_s_data[si],bx
inc size_s_data[si]
; считываем регистр управления прерываниями
mov dx,IER[si]
in al,dx
; завершаем функцию, если разрешены прерывания после передачи байта
test al,2
jnz exit_send_com
; разрешаем прерывания после передачи байта, после приема байта,
; при обнаружении состояния BREAK и при возникновении ошибки
mov al,7
out dx,al
exit_send_com:
pop si
pop dx
pop bx
pop ax
popf
pop si
mov sp,bp
pop bp
ret
_send_com ENDP
;
; S_local
;
_send_local PROC FAR
push bp
mov bp,sp
push si
mov al,[bp+6]
pushf
push ax
push bx
push si
mov si,CURRENT_AREA
test installed[si],1
jz SLX
cli
cmp size_r_data[si],R_SIZE
jb L13A
inc WORD PTR EOVFLOW[si]
jmp short L14
L13A:
mov bx,end_r_data[si]
mov reciave_buf[si][bx],al
inc bx
cmp bx,R_SIZE
jl L13
mov bx,0
L13:
mov end_r_data[si],bx
inc size_r_data[si]
L14:
sti
SLX:
pop si
pop bx
pop ax
popf
pop si
mov sp,bp
pop bp
ret
_send_local ENDP
;
; передаем удаленному модему сигнал BREAK
;
_break_com PROC FAR
push bp
mov bp,sp
push si
pushf
push ax
push cx
push dx
mov si,CURRENT_AREA
test installed[si],1
jz exit_break_com
; передаем сигнал BREAK
mov dx,LCR[si]
in al,dx
jmp $+2
or al,40h
out dx,al
mov cx,0C000h
do_BREAK:
loop do_BREAK
and al,0BFh
out dx,al
exit_break_com:
pop dx
pop cx
pop ax
popf
pop si
mov sp,bp
pop bp
ret
_break_com ENDP
;
; возвращаем в dx:ax указатель на счетчики ошибок
;
_com_errors PROC FAR
push bp
mov bp,sp
mov ax,OFFSET DGROUP:CURRENT_AREA
add ax,error_block
mov dx,ds
mov sp,bp
pop bp
ret
_com_errors ENDP
;
; заполняем счетчики ошибок
;
set_err PROC NEAR
test al,2
jz test1
inc WORD PTR EOVRUN[si]
test1:
test al,4
jz test2
inc WORD PTR EPARITY[si]
test2:
test al,8
jz test3
inc WORD PTR EFRAME[si]
test3:
test al,16
jz exit_set_err
inc WORD PTR EBREAK[si]
exit_set_err:
ret
set_err ENDP
;
; протокол модема для передачи данных
;
modem_protocol PROC NEAR
cmp device_conn[si],'M'
jne no_modem
; устанавливаем сигналы DTR, RTS и OUT2
mov dx,MCR[si]
mov al,00001011B
out dx,al
jmp $+2
; ожидаем, пока модем ответит о готовности сигналом DSR
mov cx,1000
mov dx,MSR[si]
wait_dsr:
in al,dx
test al,20H
jnz test_cts
loop wait_dsr
; модем не ответил сигналом DSR
inc WORD PTR EDSR[si]
jmp short no_modem
test_cts:
; ожидаем, пока модем ответит о готовности сигналом CTS
mov cx,1000
wait_cts:
in al,dx
test al,10H
jnz test_lcr
loop wait_cts
; модем не ответил сигналом CTS
inc WORD PTR ECTS[si]
test_lcr:
no_modem:
; проверяем, пуст ли регистр хранения передатчика
mov dx,LSR[si]
in al,dx
test al,20H
jnz s_reg_empty
; ошибка при передаче
inc WORD PTR EXMIT[si]
s_reg_empty:
ret
modem_protocol ENDP
;
; обработчик прерываний от COM1
;
int_hndlr1 PROC FAR
push si
mov si,OFFSET DGROUP:AREA1
jmp short handle_int
;
; обработчик прерываний от COM2
;
int_hndlr2 PROC FAR
push si
mov si,OFFSET DGROUP:AREA2
jmp short handle_int
;
; обработчик прерываний от COM3
;
int_hndlr3 PROC FAR
push si
mov si,OFFSET DGROUP:AREA3
jmp short handle_int
;
; обработчик прерываний от COM4
;
int_hndlr4 PROC FAR
push si
mov si,OFFSET DGROUP:AREA4
;
; обработчик прерываний
;
handle_int:
push ax
push bx
push cx
push dx
push bp
push di
push ds
push es
mov ax,DGROUP
mov ds,ax
next_pr:
; передаем контроллеру прерываний команду конца обработки
; прерывания
mov dx,OCR
mov al,eoi[si]
out dx,al
next_inter:
; считываем значение регистра идентификации прерывания
mov dx,IIR[si]
in al,dx
; определяем причину прерывания
; данные приняты и доступны для чтения
cmp al,4
je RX_int
; буфер передатчика пуст
cmp al,2
je TX_int
; изменилось состояние линий CTS, RI, DCD, DSR
cmp al,0
je MSTAT_int
; обнаружено сотояние BREAK или произошла ошибка
cmp al,6
je LSTAT_int
; завершаем обработку прерываний
jmp FAR PTR exit_handler
LSTAT_int:
; считываем регистр сотояния линии и вызываем функцию
; set_err, которая определит причину прерывания
mov dx,LSR[si]
in al,dx
call set_err
jmp next_inter
MSTAT_int:
; считываем регистр состояния модема
mov dx,MSR[si]
in al,dx
jmp next_inter
TX_int:
; смотрим, есть ли данные для передачи модему
cmp size_s_data[si],0
jg have_data_for_send
; если буфер передатчика пуст, переустанавливаем регистр
; управления прерываниями
mov dx,IER[si]
mov al,5
out dx,al
jmp next_inter
have_data_for_send:
; передаем символ модему в соответствии с состоянием
; линий RS-232-С
call modem_protocol
; передаем очередной символ из буфера передатчика
mov bx,start_s_data[si]
mov al,send_buf[si][bx]
mov dx,DATREG[si]
out dx,al
inc bx
cmp bx,S_SIZE
jb ptr_no_max
mov bx,0
ptr_no_max:
mov start_s_data[si],bx
dec size_s_data[si]
jmp next_inter
; данные приняты и доступны для чтения
RX_int:
; считываем принятый байт из регистра данных UART
mov dx,DATREG[si]
in al,dx
cmp size_r_data[si],R_SIZE
jl no_r_EOVFLOW
; буфер приемника переполнен, увеличиваем соответствующий
; счетчик ошибок
inc WORD PTR EOVFLOW[si]
jmp next_inter
no_r_EOVFLOW:
mov bx,end_r_data[si]
mov reciave_buf[si][bx],al
inc size_r_data[si]
inc bx
cmp bx,R_SIZE
jb no_max_r_ptr
mov bx,0
no_max_r_ptr:
mov end_r_data[si],bx
jmp next_inter
exit_handler:
mov al,20h
out 20h,al
pop es
pop ds
pop di
pop bp
pop dx
pop cx
pop bx
pop ax
pop si
iret
int_hndlr4 ENDP
int_hndlr3 ENDP
int_hndlr2 ENDP
int_hndlr1 ENDP
COM_TEXT ENDS
END
Теперь мы приведем исходные тексты включаемых файлов, используемых нашей
программой. // COMMON.H
#define equal(a,b) (!strcmp(a,b))
#define equali(a,b) (!stricmp(a,b))
#define equalni(a,b,n) (!strnicmp(a,b,n))
#define equaln(a,b,n) (!strncmp(a,b,n))
#define nil(type) ((type *)NULL)
#define boolean unsigned
#define TRUE 1
#define FALSE 0
Файл CONF.H #define OPERATOR(x) !strncmp(LineBuf,(x),strlen((x)))
#define STRIP(x) (x)[strlen(x)-1] = 0;
Файл GET_WORD.H unsigned expectstr(char *Search, unsigned int Timeout);
void sendstr(char *str);
Файл MOD_LINK.H #define CONN_INITIALIZE 1
#define CONN_CALLUP 2
#define CONN_HOTMODEM 3
#define CONN_ANSWER 4
#define CONN_LOGIN 5
#define CONN_HOTLOGIN 6
#define CONN_PROTOCOL 7
#define CONN_SERVER 8
#define CONN_CLIENT 9
#define CONN_TERMINATE 10
#define CONN_DROPLINE 11
#define CONN_EXIT 12
#define CONN_STATE char
Файл MODEM.H int call( void );
int answer( void );
void slowwrite( char *s, int len);
void shutdown( void );
extern char *device;
Файл TIMER.H void sleep(time_t interval);
void delay(int milliseconds);
Файл TOOLS.H extern unsigned port_active;
int openline(char *name, unsigned baud);
unsigned int sread(char *buffer,
unsigned int wanted,
unsigned int timeout);
int swrite(char *data, unsigned int len);
void ssendbrk(unsigned int duration);
void closeline(void);
Файл TOTAL.H #define WHITESPACE " \t\n\r"
#define equal(a,b) (!strcmp(a,b))
#define equali(a,b) (!stricmp(a,b))
#define equalni(a,b,n) (!strnicmp(a,b,n))
#define equaln(a,b,n) (!strncmp(a,b,n))
#define nil(type) ((type *)NULL)
Файл UART.H /**
*.Name select_port
*
*.Title Выбираем используемый COM-порт
*
*.Descr Эта функция определяет, с каким COM-портом
* мы в дальнейшем будем работать.
*
*.Proto void far select_port(int port)
*
*.Params int port - номер порта (COM1..COM4)
*
*.Return не используется
*
*.Sample comm.asm
**/
void far select_port(int);
/**
*.Name save_com
*
*.Title Сохраняем старый вектор прерывания COM-порта
*
*.Descr Эта функция сохраняет адрес старого обработчика
* прерываний от COM-порта.
*
*.Proto void far save_com(void)
*
*.Params не используется
*
*.Return не используется
**/
void far save_com(void);
/**
*.Name restore_com
*
*.Title Восстанавливаем старый вектор прерывания COM-порта
*
*.Descr Эта функция восстанавливает адрес старого обработчика
* прерываний от COM-порта.
*
*.Proto void far restore_com(void)
*
*.Params не используется
*
*.Return не используется
**/
void far restore_com(void);
/**
*.Name install_com
*
*.Title Устанавливаем свой обработчик прерывания COM-порта
*
*.Descr Эта функция устанавливает новый обработчик
* прерываний от COM-порта.
*
*.Proto int far install_com(void)
*
*.Params не используется
*
*.Return возвращает 1 при успешной установке
* и 0 в случае ошибки
**/
int far install_com(void);
/**
*.Name install_com
*
*.Title Устанавливаем свой обработчик прерывания COM-порта
*
*.Descr Эта функция устанавливает новый обработчик
* прерываний от COM-порта.
*
*.Proto void far open_com( int baud, int device,
* int parity, int stop_bits )
*
*.Params int baud - скорость обмена
* int device - тип устройства связи: M - модем
* D - нуль-модем
* int parity - проверка на четность: N - нет проверки
* на четность, O - проверка по нечетности,
* E - проверка по четности, S - Space,
* M - Mark
* int stop_bits - количество стоповых бит
*
*.Return не используется
**/
void far open_com( int, int, int, int );
/**
*.Name close_com
*
*.Title Запрещаем прерывания от асинхронного порта COM-порта
*
*.Descr Эта функция запрещает прерывания от COM-порта
*
*.Proto void far close_com(void)
*
*.Params не используется
*
*.Return не используется
**/
void far close_com(void); /* close com port */
/**
*.Name dtr_off
*
*.Title Сбрасываем сигнал DTR
*
*.Proto void far dtr_off(void)
*
*.Params не используется
*
*.Return не используется
**/
void far dtr_off(void);
/**
*.Name dtr_on
*
*.Title Устанавливаем сигнал DTR
*
*.Proto void far dtr_on(void)
*
*.Params не используется
*
*.Return не используется
**/
void far dtr_on(void);
/**
*.Name r_count
*
*.Title Определяем состояние буфера приемника
*
*.Proto long far r_count(void)
*
*.Params не используется
*
*.Return в младшем байте - число байт в буфере приемника,
* а в старшем - общий размер буфера приемника
*
**/
long far r_count(void);
// макроопределение r_count_size возвращает
// общий размер буфера приемника
#define r_count_size() ((int)(r_count() >> 16))
// макроопределение r_count_pending возвращает
// число байт в буфере приемника
#define r_count_pending() ((int)r_count())
/**
*.Name receive_com
*
*.Title Получить один символ из буфера приемника
*
*.Proto int far receive_com(void)
*
*.Params не используется
*
*.Return очередной символ из буфера приемника
**/
int far receive_com(void);
/**
*.Name s_count
*
*.Title Определяем состояние буфера приемника
*
*.Proto long far s_count(void)
*
*.Params не используется
*
*.Return в младшем байте - число свободных байт в буфере передатчика,
* а в старшем - общий размер буфера передатчика
*
**/
long far s_count(void);
// макроопределение s_count_size возвращает
// общий размер буфера приемника
#define s_count_size() ((int)(s_count() >> 16))
// макроопределение s_count_pending возвращает
// число байт в буфере приемника
#define s_count_free() ((int)s_count())
/**
*.Name send_com
*
*.Title Передать один символ в буфер передатчика
*
*.Proto void far send_com(int ch)
*
*.Params int ch - передаваемый символ
*
*.Return не используется
**/
void far send_com(int);
/**
*.Name break_com
*
*.Title Передать сигнал BREAK удаленному модему
*
*.Proto void far break_com(void)
*
*.Params не используется
*
*.Return не используется
**/
void far break_com(void);
/**
*.Name com_errors
*
*.Title Возвращает указатель на массив счетчиков ошибок
*
*.Proto int far *far com_errors(void)
*
*.Params не используется
*
*.Return не используется
**/
int far *far com_errors(void);
#define COM_EOVFLOW 0 // переполнен буфер
#define COM_EOVRUN 1 // ошибка переполнения при приеме
#define COM_EBREAK 2 // обнаружен запрос на прерывание
#define COM_EFRAME 3 // ошибка синхронизации
#define COM_EPARITY 4 // ошибка четности
#define COM_EXMIT 5 // ошибка при передаче
#define COM_EDSR 6 // не получен сигнал dsr
#define COM_ECTS 7 // не получен сигнал cts
Как мы говорили ранее, одним из преимуществ внешнего модема над внутренним
является наличие на его лицевой панели световых индикаторов, по которым можно
определить состояние модема.
Мы приведем маленькую резидентную программу, которая отображает на экране
дисплея состояние модема. Наша программа компенсирует недостаток внутреннего
модема, связанный с отсутствием индикации.
Программу можно также использовать при отладке своих приложений, работающих с
COM-портами, так как фактически она отображает состояние регистров микросхемы
UART.
Как работает эта программа? Она перехватывает прерывание от таймера (с
номером 1Сh) и таким образом получает управление несколько раз в секунду. Затем
она считывает значения регистров состояния линии, состояния модема и регистра
управления модемом.
По состоянию регистра управления модемом программа определяет состояние линий
DTR и RTS, управляющих процессом подтверждения связи. Регистр состояния модема
отражает состояние сигналов CTS, DSR, используемых для управления потоком, а
также линий RI и DCD. Сигнал RI означает, что пришел вызов от удаленного модема,
а сигнал DCD - что модемы установили и поддерживают связь.
После того как программа считает состояние регистров асинхронного порта, она
отображает их состояние на экране дисплея. Вывод данных на экран наша программа
выполняет путем непосредственного доступа к видеопамяти. При этом данные
выводятся, начиная с адреса B800:0000. Если вы собираетесь использовать
программу в графических режимах видеоадаптера или отображать данные о сотоянии
регистров в другом месте экрана, то вам надо самим изменить процедуру вывода.
После запуска программы в левом верхнем углу экрана появится небольшое окно,
в котором будет отображаться состояние линий TD, RD, RTS, CTS, RI, CDC, DTR,
DSR: --T-T-T-T-T-T-T-
t r r c r d d d
d d t t i c t s
s s d r r
LT+T+T+T+T+T+T+T-
L---- сигнал DSR
L------ сигнал DTR
L-------- сигнал DCD
L---------- сигнал RI
L------------ сигнал CTS
L-------------- сигнал RTS
L---------------- данные принимаются
L------------------ данные передаются
Если какой-нибудь из этих сигналов примет значение логической единицы, то
первый символ в названии этого сигнала будет отображаться с заглавной буквы.
Например, если модем установил связь и передает данные, будут активными линии
TD, RTS, CTS, DCD, DTR и DSR: T r R C r D D D
d d t t i c t s
s s d r r
Итак, текст программы: // LIGHT.C
// сигнальные лампы модема
#include <stdlib.h>
#include <string.h>
#include <dos.h>
// включаемый файл uart_reg.h можно найти в приложении
#include "uart_reg.h"
// отключаем проверку стека и указателей
#pragma check_stack( off )
#pragma check_pointer( off )
// количество прерываний от таймера за одну секунду
#define TICKPERMIN 1092L
char _huge *tsrstack;
char _huge *appstack;
char _huge *tsrbottom;
// объявления функций
void (_interrupt _far *oldtimer)( void );
void _interrupt _far newtimer( void );
void test_com_port( void );
void hello( void );
void help( void );
// определяем указатель на счетчик таймера
long _far *pcurtick = (long _far *)0x0000046cL;
long goaltick;
// следующий массив содержит возможные базовые адреса
// различных последовательных портов
unsigned uart_reg[] = { 0, 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
unsigned com_port;
// главная процедура
void main( int argc, char *argv[] ) {
unsigned tsrsize;
// параметр программы должен содержать номер
// используемого COM-порта
if( argc < 2 ) help();
hello();
_asm mov WORD PTR tsrstack[0], sp
_asm mov WORD PTR tsrstack[2], ss
FP_SEG( tsrbottom ) = _psp;
FP_OFF( tsrbottom ) = 0;
com_port = ( unsigned ) uart_reg[atoi( argv[1] )];
printf( "\nпорт %x\n", com_port );
// определяем момент первой проверки регистров
// асинхронного адаптера
goaltick += TICKPERMIN / 10L;
// переустанавливаем вектор прерывания от таймера
oldtimer = _dos_getvect( 0x1c );
_dos_setvect( 0x1c, newtimer );
// вычисляем размер резидентной части программы
tsrsize = ((tsrstack - tsrbottom) >> 4) + 1;
_dos_freemem( _psp );
// оставляем программу резидентной в памяти
_dos_keep( 0, tsrsize );
}
void hello( void ) {
printf("\n(c) Frolov G.V. Сигнальные лампы\n \n"
" trrcrddd \n"
" ddtticts \n"
" ss drr \n"
" \n"
" L---- сигнал DSR \n"
" L----- сигнал DTR \n"
" L------ сигнал DCD \n"
" L------- сигнал RI \n"
" L-------- сигнал CTS \n"
" L--------- сигнал RTS \n"
" L---------- данные принимаются \n"
" L----------- данные передаются \n"
);
}
void help( void ) {
printf(" Неправильно задан параметр программы - \n"
" пример: LIGHT [1|2|3|4] \n");
exit(0);
}
// обработчик прерываний от таймера
void _interrupt _far newtimer() {
// если пришло время, проверяем регистры COM-порта
if(*pcurtick >= goaltick) {
(*oldtimer)();
// вычисляем время следующей проверки
goaltick += TICKPERMIN / 10L;
// проверяем регистры COM-порта и отображаем их на экране
test_com_port();
}
else
_chain_intr( oldtimer );
}
void test_com_port( void ) {
char data[] = "trrcrddd";
char data2[] = "ddtticts";
char data3[] = " ss drr";
char attr[9];
_asm {
// получаем адрес регистра состояния линии
mov dx,com_port
add dx,LSR_N
in al,dx
// проверяем, пуст ли регистр сдвига передатчика
test al,40h
jz next_1
mov ah,'t'
mov data[0],ah
mov attr[0],0x71
jmp short next_2
next_1:
// если регистр сдвига не пуст, значит, модем
// осуществляет передачу данных
mov ah,'T'
mov data[0],ah
mov attr[0],0x79
next_2:
// проверяем, есть ли данные, готовые для чтения
test al,1
jnz next_3
mov ah,'r'
mov data[1],ah
mov attr[1],0x71
jmp short next_4
next_3:
// если есть данные для чтения, отображаем на экране
// символ 'R'
mov ah,'R'
mov data[1],ah
mov attr[1],0x79
next_4:
// считываем регистр состояния модема
mov dx,com_port
add dx,MSR_N
in al,dx
// определяем состояние линии CTS
test al,10h
jnz next_5
// линия CTS неактивна
mov ah,'c'
mov data[3],ah
mov attr[3],0x72
jmp short next_6
next_5:
// линия CTS активна
mov ah,'C'
mov data[3],ah
mov attr[3],0x7a
next_6:
// определяем состояние линии DSR
test al,20h
jnz next_7
// линия DSR неактивна
mov ah,'d'
mov data[7],ah
mov attr[7],0x72
jmp short next_8
next_7:
// линия DSR активна
mov ah,'D'
mov data[7],ah
mov attr[7],0x7a
next_8:
// определяем состояние линии RI
test al,40h
jnz next_9
// линия RI находится в неактивном состоянии
mov ah,'r'
mov data[4],ah
mov attr[4],0x76
jmp short next_10
next_9:
// линия RI находится в активном состоянии
mov ah,'R'
mov data[4],ah
mov attr[4],0x7e
next_10:
// определяем состояние линии DCD
test al,80h
jnz next_11
// линия DCD неактивна
mov ah,'d'
mov data[5],ah
mov attr[5],0x76
jmp short next_12
next_11:
// линия DCD активна
mov ah,'D'
mov data[5],ah
mov attr[5],0x7e
next_12:
// считываем регистр управления модемом
mov dx,MCR_N
add dx,com_port
in al,dx
// проверяем линию RTS
test al,2
jnz next_13
// линия RTS находится в неактивном состоянии
mov ah,'r'
mov data[2],ah
mov attr[2],0x74
jmp short next_14
next_13:
// линия RTS находится в активном состоянии
mov ah,'R'
mov data[2],ah
mov attr[2],0x7c
next_14:
// проверяем линию DTR
test al,1
jnz next_15
// линия DTR неактивна
mov ah,'d'
mov data[6],ah
mov attr[6],0x74
jmp short next_16
next_15:
// линия DTR активна
mov ah,'D'
mov data[6],ah
mov attr[6],0x7c
next_16:
// отображаем на экране состояние регистров
// вывод на экран происходит непосредственно
// через видеопамять
// сегмент видеопамяти
mov ax,0B800h
mov es,ax
xor si,si
mov di,0
mov cx,8
next_char:
// отображаем на экране очередной символ
// из массива data
mov ah,data[si]
mov es:[di],ah
inc di
// устанавливаем атрибуты этого символа
mov ah,attr[si]
mov es:[di],ah
sti
inc di
inc si
loop next_char
xor si,si
mov di,160
mov cx,8
next_str2:
mov ah,data2[si]
mov es:[di],ah
inc di
mov ah,attr[si]
mov es:[di],ah
sti
inc di
inc si
loop next_str2
xor si,si
mov di,320
mov cx,8
next_str3:
mov ah,data3[si]
mov es:[di],ah
inc di
mov ah,attr[si]
mov es:[di],ah
sti
inc di
inc si
loop next_str3
}
}
|