Программирование модемов© Александр Фролов, Григорий ФроловТом 4, М.: Диалог-МИФИ, 1993, 236 стр. 5.3. Коммуникационная программаТеперь мы приступим к самому интересному - приведем подробный алгоритм коммуникационной программы, а затем - исходный текст такой программы. Сначала мы рассмотрим вариант коммуникационной программы без использования прерываний от асинхронного порта. Этот вариант несколько проще, так как нам не надо создавать довольно нетривиальный обработчик для этого прерывания, а также программировать контроллер прерываний. Итак, приступим. Как мы сказали ранее, первым шагом при программировании модема надо считать инициализацию COM-порта (микросхемы UART), к которому подключен модем. Инициализация COM-портаСначала надо перевести в неактивное состояние линии DTR и RTS, которые сообщают модему, что компьютер готов к обмену данными. Для этого надо записать нулевое значение в регистр управления модемом: mov al,0 ; сбрасываем сигналы DTR и RTS Затем сбрасываем регистры состояния линии, состояния модема и данных. Это достигается простым считыванием значений этих регистров: ; сбрасываем регистр состояния линии 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() позволяет передать на модем только один символ. Передача символа осуществляется следующим образом:
Функция 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; } } |