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

Исходный текст коммуникационной программы 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; } }


Содержание раздела