Исходный текст коммуникационной программы S_CHAT
Исходный текст коммуникационной программы 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 2512762". Эта команда включает динамик модема (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; } }