Данная глава книги будет посвящена программированию асинхронных последовательных адаптеров и модемов в среде операционной системы MS-DOS. Мы опишем основные принципы работы телекоммуникационных программ с использованием и без использования прерываний от асинхронного адаптера.
В этой главе вы найдете исходные тексты небольших телекоммуникационных программ, предназначенных для работы в среде операционной системы MS-DOS.
Доступ к модему происходит через последовательный асинхронный порт. При этом для передачи модему команд их необходимо просто записать в регистр данных 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, значит, не удалось образовать соединение с удаленным модемом и надо попытаться повторить соединение.
Для разных модемов и разных режимов его работы набор сообщений может быть шире, чем мы указали в таблицах. Получить больше информации о сообщениях модема можно в разделе "Система команд hayes-модемов" и документации модема. Особое внимание следует обратить на команды Q, V, X.
После окончания работы коммуникационная программа должна перевести модем в командный режим и передать ему команду положить трубку (ATH0). Для перевода модема в командный режим можно воспользоваться Escape-последовательностью +++. После того как модем перешел в командный режим, можно опять передавать ему AT-команды.
Низкий уровень сигналов DTR и RTS сообщает модему, что компьютер не готов к приему данных через COM-порт.
Теперь мы приступим к самому интересному - приведем подробный алгоритм коммуникационной программы, а затем - исходный текст такой программы.
Сначала мы рассмотрим вариант коммуникационной программы без использования прерываний от асинхронного порта. Этот вариант несколько проще, так как нам не надо создавать довольно нетривиальный обработчик для этого прерывания, а также программировать контроллер прерываний.
Итак, приступим. Как мы сказали ранее, первым шагом при программировании модема надо считать инициализацию COM-порта (микросхемы UART), к которому подключен модем.
Программа, представленная в предыдущей главе, имеет один большой недостаток: она должна постоянно выполнять опрос регистра состояния линии с тем, чтобы определить момент, когда от модема поступит очередной символ. В результате становится трудной, а иногда невозможной, обработка поступающих символов. Например, если вы сразу отображаете символы, получаемые от COM-порта, на экране, то при использовании для этого функции putch отдельные символы могут быть потеряны. Дело в том, что функция putch работает слишком медленно и на скоростях 2400 бит/с и выше модем может успеть передать в COM-порт несколько новых символов, в то время как функция putch еще не вывела на экран ни одного символа. В этом случае происходит ошибка переполнения входного буфера микросхемы UART (см. бит D2 регистра состояния линии).
Таким образом, имеет смысл организовать прием и передачу символов модему в фоновом режиме, используя прерывания по окончании приема и передачи символа.
Если ваша коммуникационная программа будет использовать прерывания, можно организовать буфер принимаемых и передаваемых данных. Обработчик прерываний должен проанализировать причину прерывания и либо передать в COM-порт очередной символ из буфера передатчика (если прерывание произошло в результате передачи очередного символа), либо считать поступивший символ из регистра данных и записать его в буфер приемника (если прерывание произошло в результате приема от модема очередного символа).
В этом случае процесс обмена идет в фоновом режиме и процессор может спокойно заниматься обработкой принимаемых и передаваемых символов. Если программе понадобится передать данные модему, она может просто записать их в буфер передатчика. Для приема данных она должна считать их из буфера приемника.
При работе с асинхронным последовательным адаптером (COM-портом) вы можете использовать механизм прерываний. Глава "Программирование асинхронного адаптера" содержит теоретические сведения по этому вопросу, а в главе "Коммуникационная программа, использующая прерывания" содержится исходный текст коммуникационной программы, использующей прерывания для работы с COM-портом. Если ваша программа использует прерывания от COM-порта, она должна содержать обработчик прерываний, а также программировать контроллер прерываний для разрешения прерываний.
Так как передача и прием данных модемом представляют собой длительный процесс, то применение прерываний от 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.
Что представляет из себя обработчик прерываний асинхронного адаптера? На рисунке 6.1 мы привели блок схему такого обработчика прерываний.
Рис. 6.1. Блок схема обработчика прерываний
Теперь опишем алгоритм обработки прерываний от асинхронного последовательного адаптера. Этот алгоритм реализован в программе CHATINT, представленной ниже.
Необходимо выполнить команду 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.
Итак, мы завершили рассмотрение обработчика прерываний. Теперь нам осталось изучить примерный порядок работы коммуникационной программы с использованием прерываний.
Необходимо установить обработчик прерываний, изменив соответствующий элемент таблицы векторов прерываний. Адрес старого обработчика сохраняется в глобальных переменных.
Сначала надо перевести в неактивное состояние линии 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
Затем нужно восстановить старый вектор обработчика прерываний.
В этой главе мы приведем исходный текст коммуникационной программы CHATINT, использующей для работы с портами асинхронного адаптера механизм прерываний.
При помощи этой программы можно связаться с удаленным модемом, передавать и принимать от него данные в формате ASCII. Например, вы можете позвонить на станцию BBS и прочитать почтовые сообщения. Передачу и прием файлов программа не поддерживает, иначе ее исходный текст занимал бы слишком много места в книге.
Данная телекоммуникационная программа может работать в двух режимах - активном, когда она сама производит вызов удаленного модема, и пассивном, когда программа находится в режиме ожидания звонка от удаленного модема. Для работы программы в активном режиме необходимо запустить ее с параметром "1", для пассивного режима - "0".
Большинство параметров программы, таких, как номер COM-порта, к которому подключен модем, скорость обмена данными, AT-команды инициализации модема и телефонный номер вызываемого абонента можно настроить через файл конфигурации SETUP.CFG. Образец этого файла представлен в листинге 6.1.
Листинг 6.1. Файл 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
Исходные тексты программы CHATINT включают в себя несколько модулей на языке Си и один модуль на языке ассемблера. Ниже перечислены названия исходных файлов программы:
Имена файлов |
Содержит |
CHATINT.C |
Главная функция программы |
MODEM.C |
Передача данных модему через COM-порт |
TIMER.C |
Реализация временных задержек |
CONF.C |
Чтение и обработка файла конфигурации |
SEND_COMM.C |
Передача команд модему |
TOOLS.C |
Набор функций для работы с модулем UART.ASM |
UART.ASM |
Обработчик прерываний и процедуры низкого уровня |
Все эти файлы, а также файлы с исходными текстами других программ, приведенных в книге, можно отдельно приобрести на дискете.
Теперь приведем сами исходные тексты программы. Основной модуль программы называется CHATINT.C (см. листинг 6.2). В зависимости от параметра программы этот модуль вызывает функцию Call - для вызова удаленного модема - или функцию Answer - для ответа на приходящие звонки.
Если программа CHATINT запущена без параметров, управление передается функции Hello. Функция Hello отображает на экране справочную информацию. Затем программа завершается.
После окончания сеанса связи вызывается функция Shutdown, которая опускает телефонную трубку и отключает обработчик прерываний.
Функции Call, Answer и Shutdown, выполняют все действия по программированию COM-порта, контроллера прерываний и модема. Данные функции определены в модуле MODEM.C. Исходные тексты модуля MODEM.C приведены в листинге 6.4.
Листинг 6.2. Файл CHATINT.C
//======================================================= // Основной модуль коммуникационной программы //======================================================= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <sys/types.h> #include "common.h" #include "modem.h" #include "timer.h" #include "tools.h" //======================================================= // Функция Hello //======================================================= void Hello(void) { printf("Неправильно задан параметр программы \n" "CHATINT n, где n = 1 режим вызова, " "n = 0 режим ответа\n"); exit(0); } // Основная процедура void main( int argc, char *argv[] ) { // Программа должна вызываться параметром 1 или 0 if( argc < 2 ) Hello(); 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(); }
В файле CONF.C определена функция GetConfig, считывающая файл конфигурации SETUP.CFG и заполняющая соответствующими значениями глобальные переменные (см. листинг 6.3).
Функция GetConfig открывает файл конфигурации SETUP.CFG и начинает считывать его содержимое по одной строке. Каждое первое слово из строки сравнивается с ключевыми словами, приведенными в следующей таблице. В случае совпадения заполняется соответствующая глобальная переменная. Глобальные переменные определены в файле MODEM.C (см. листинг 6.4).
Ключевое слово |
Глобальная переменная |
Описание |
Dropline |
dropline |
Команда модему для разрыва связи с удаленным модемом |
Initialize |
initialize |
Команда инициализации модема |
AutoAnswer |
autoanswer |
Команда инициализации модема для работы в режиме автоответа на
приходящие звонки |
DialNumber |
dialNumber |
Номер удаленного модема |
DialPrefix |
dialPrefix |
Префикс для команды набора номера |
DialSuffix |
dialSuffix |
Суффикс для команды набора номера |
Device |
device |
Номер COM-порта, к которому подключен модем |
CharDelay |
chardelay |
Задержка между отдельными символами, передаваемыми модему |
DialTimeout |
dialTimeout |
Интервал времени, за который модем должен набрать номер и
установить связь с удаленным модемом |
ModemTimeout |
modemTimeout |
Интервал времени, за который модем должен ответить на
передаваемые ему команды |
TimeoutAnswer |
answerTimeout |
Время, отведенное на установку связи с удаленным модемом |
Speed |
speed |
Скорость обмена данными через COM-порт |
По достижении конца файла SETUP.CFG файл закрывается, и функция возвращает управление.
Листинг 6.3. Файл 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]; // Глобальные переменные, заполняемые функцией GetConfig 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; //============================================================= // Функция GetConfig //============================================================= 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("AutoAnswer")) 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); } //===================================================== // Функция Token //===================================================== void Token( char *string ) { char *next; next = strcpy( string, strchr( LineBuf, ' ' ) + 1 ); if(next == NULL) string[0] = '\0'; } //===================================================== // Функция Number //===================================================== void Number( unsigned *num ) { char buf[80]; strcpy( buf, strchr( LineBuf, ' ' ) + 1 ); *num = atoi( buf ); }
В файле MODEM.C, представленном на листинге 6.4, определены основные функции высокого уровня для работы с модемом:
Функция |
Назначение |
Call |
Определяет работу программы в режиме вызова удаленного модема |
Answer |
Определяет работу программы в режиме ответа на приходящие
звонки |
Shutdown |
Выполняет завершение сеанса связи |
Exchange |
Поддерживает диалог пользователя и удаленного модема. |
Эти функции вызывают модули более низкого уровня: SENDCMD.C и TOOLS.C.
Листинг 6.4. Файл MODEM.C
#include <stdio.h> #include <conio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <sys/types.h> #include "uart.h" #include "common.h" #include "modem.h" #include "sendcmd.h" #include "timer.h" #include "tools.h" #include "conf.h" // Номер используемого порта в формате COMn, где n от 1 до 4 char *device = "COM3"; // Продолжительность ожидания соединения unsigned dialTimeout = 12; // Задержка при передаче между символами unsigned chardelay = 0; // Таймаут на получение ответа от модема unsigned modemTimeout = 3; // Продолжительность ожидания звонка unsigned answerTimeout; // Скорость обмена данными unsigned speed = 2400; char initialize[80]; // команда инициализации char dropline[80]; // команда повесить трубку char autoanswer[80]; // ответ на вызов в режиме автоответа char dialPrefix[80]; // префикс номера char dialSuffix[80]; // суффикс номера char dialNumber[80]; // телефонный номер // Прототип функции Exchange 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( RCountPending() > 0 ) { SRead(str, i = RCountPending(), 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( ); flag = 0; break; } if( key == '\r' ) putch( '\n' ); putch(key); SWrite( &key, 1); } // Если получены данные от модема, отображаем их на экране if( RCountPending() > 0 ) { Delay(100); SRead(str, i = RCountPending(), 0); str[i] = '\0'; for( j = 0; j < i; j++ ) putch( str[j] ); } } }
Следующий модуль - SENDCMD.C определяет функцию SendStr, которая используется для передачи модему AT-команд. Исходные тексты файла SENDCMD.C приведены в листинге 6.5.
Листинг 6.5. Файл SENDCMD.C
#include <string.h> #include <time.h> #include "common.h" #include "modem.h" #include "sendcmd.h" #include "timer.h" #include "tools.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, представленный в листинге 6.6, содержит определения функций для работы с модулем UART.ASM.
Листинг 6.6. Файл 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; static unsigned hangup_needed = TRUE; #define STOPBIT 1 /** *.Name OpenLine * *.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-порт SelectPort(value); // Сохраняем адрес старого обработчика прерываний COM-порта SaveCom(); // Устанавливаем новый обработчик прерываний COM-порта InstallCom(); // Программируем текущий COM-порт // скорость, связь через модем, не проверяем четность // один стоповый бит OpenCom(baud, 'M', 'N', STOPBIT); // Запоминаем скорость current_baud = baud; // Устанавливаем линию DTR в активное состояние // (компьютер готов к обмену данными) DtrOn(); port_active = TRUE; return( 0 ); } /** *.Name SRead * *.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 = RCountPending(); // Если в буфере ессть необходимое количество символов if (pending >= wanted) { unsigned int i; // Считывааем из буфера нужное число символов for (i = 0; i < wanted; i++) *buffer++ = (char) ReceiveCom(); return pending; } // Если в буфере приемника меньше символов, чем заказано // для чтения, проверяем, не истекло ли отведенное для // чтения время else { time_t now = time(nil(time_t)); time_t elapsed = now - start; Delay(0); if (elapsed >= (long) timeout) return pending; } } } /** *.Name SWrite * *.Descr Функция записывает заданное число символов * в буфер передатчика асинхронного порта. * *.Proto int SWrite(char *data, unsigned len) * *.Params char *data - указатель на буфер данных * * unsigned len - число символов, которое надо записать * в буфер передатчика * *.Return количество символов записанных в буфер передатчика **/ int SWrite(char *data, unsigned int len) { unsigned int i; hangup_needed = TRUE; // Записываем входные данные в буфер передатчика // асинхронного порта for (i = 0; i < len; i++) SendCom(*data++); return len; } /** *.Name SSendBrk * *.Title Передает сигнал BREAK удаленному модему. * *.Proto void SSendBrk() * *.Params Не используются. * *.Return Не используется. **/ void SSendBrk(void) { BreakCom(); } /** *.Name CloseLine * *.Descr Функция восстанавливает старые значение * векторов прерываний и запрещает прерывания * от COM-порта. * *.Proto void CloseLine(void) * *.Params Не используются. * *.Return Не используется. **/ void CloseLine(void) { int far *stats; port_active = FALSE; // Отменяем сигнал DTR DtrOff(); // Запрещаем прерывания от COM-порта CloseCom(); // Восстанавливаем вектора прерываний RestoreCom(); }
Вспомогательный модуль TIMER.C (см. листинг 6.7) содержит определения функций Sleep и Delay. Эти функции используются в программе для организации временных задержек.
Функция Delay служит для организации небольших задержек. Единственный параметр этой функции определяет величину задержки в миллисекундах.
Функция Sleep циклически вызывает функцию Delay и позволяет организовывать более длительные задержки. В качестве параметра для этой функции следует указать величину задержки в секундах.
Листинг 6.7. Файл TIMER.C
#include <time.h> #include <stdio.h> #include <sys/timeb.h> #include "timer.h" /** *.Name Sleep * *.Descr Функция приостанавливает выполнение * программы на заданное число секунд. * *.Proto void Sleep(time_t interval) * *.Params time_t interval - время задержки в секундах * *.Return Не используется **/ 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 * *.Descr Функция приостанавливает выполнение * программы на заданное число милисекунд. * *.Proto void Delay(int milliseconds) * *.Params time_t interval - время задержки в милисекундах * *.Return Не используется **/ 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; } }
Включаемый файл COMMON.H содержит макроопределения нескольких функций, предназначенных для работы со строками. Исходный текст файла COMMON.H содержится в листинге 6.8.
Листинг 6.8. Файл 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 (см. листинг 6.9) содержит макроопределения OPERATOR и STRIP, используемые в модуле CONF.C, а также описание функции GetConfig.
Листинг 6.9. Файл CONF.H
#define OPERATOR(x) !strncmp(LineBuf,(x),strlen((x))) #define STRIP(x) (x)[strlen(x)-1] = 0; void GetConfig(void);
Модуль UART.ASM - это основной модуль программы CHAT. Он содержит обработчик прерываний от COM-порта и функции низкого уровня для работы с ним. Исходный текст модуля UART.ASM представлен в листинге 6.10.
Обработчик прерываний имеет два буфера - буфер приемника и буфер передатчика. Через эти буферы осуществляется обмен данными между программой и обработчиком прерываний. Буферы выполнены в виде очереди.
Опишем функции, определенные в модуле UART.ASM.
Функция |
Назначение |
SelectPort |
Определяет, с каким COM-портом мы в дальнейшем будем работать.
Единственный параметр функции должен содержать номер COM-порта. |
SaveCom |
Сохраняет адрес старого обработчика прерываний от COM-порта.
Функция не имеет параметров и не возвращает никакого значения. |
RestoreCom |
Восстанавливает адрес старого обработчика прерываний от
COM-порта, ранее сохраненного функцией SaveCom. |
InstallCom |
Устанавливает новый обработчик прерываний от COM-порта. Она
должна вызываться, после того как адрес старого обработчика прерываний
сохранен с помощью функции SaveCom. Функция возвращает единицу при успешной
установке обработчика или ноль в случае ошибки. |
OpenCom |
Инициализирует регистры асинхронного адаптера. Первый параметр
функции baud определяет скорость обмена данными через COM-порт. Второй параметр
- device задает тип устройства связи: Для модемов он должен содержать код
символа 'M', а для нуль-модема - 'D'. Третий параметр parity - управляет
проверкой на четность. Если он содержит код символа 'N' - проверка не
производится, 'O' - выполняется проверка по нечетности, 'E' - проверка по
четности, 'S' - бит четности всегда сброшен и 'M' - бит четности установлен.
Последний параметр - stop_bits - задает количество стоповых битов. |
CloseCom |
Запрещает прерывания от COM-порта. После ее вызова обмен
данными через COM-порт прекращается. |
DtrOn |
Устанавливает сигнал DTR |
DtrOff |
Сбрасывает сигнал DTR |
RCount |
Проверяет состояние буфера приемника. Младший байт значения,
возвращаемого функцией, определяет количество байтов в буфере приемника, а
старший - общий размер буфера приемника. |
ReceiveCom |
Читает один символ из буфера приемника и возвращает его
значение. |
SCount |
Возвращает в младшем байте число свободных байтов в буфере
передатчика, а в старшем - общий размер буфера передатчика. |
SendCom |
Позволяет записать один символ в буфер передатчика.
Единственный параметр функции должен содержать код передаваемого символа. |
BreakCom |
Переводит передающую линию в состояние BREAK. |
ComErrors |
Возвращает указатель на массив счетчиков ошибок. Во включаемом
файле UART.H определен набор констант для доступа к отдельным полям этого
массива. |
Листинг 6.10. Файл UART.ASM
; Определяем размеры буфера приемника и передатчика 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 ; **** Область данных для каждого порта **** ; Область данных COM1 AREA1 SP_TAB <1,INT_COM1,E_IRQ4,D_IRQ4,EOI4> ; Область данных COM2 AREA2 SP_TAB <2,INT_COM2,E_IRQ3,D_IRQ3,EOI3> ; Область данных COM3 AREA3 SP_TAB <3,INT_COM3,E_IRQ4,D_IRQ4,EOI4> ; Область данных COM4 AREA4 SP_TAB <4,INT_COM4,E_IRQ3,D_IRQ3,EOI3> _DATA ENDS COM_TEXT SEGMENT PARA public 'CODE' ASSUME cs:COM_TEXT,ds:DGROUP,es:NOTHING public _SelectPort public _SaveCom public _InstallCom public _RestoreCom public _OpenCom public _CloseCom public _DtrOn public _DtrOff public _RCount public _SCount public _ReceiveCom public _SendCom public _BreakCom public _ComErrors ;======================================================= ; Выбор активного порта ; [bp+6] - номер порта _SelectPort 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 _SelectPort ENDP ;======================================================= ; Сохранение текущего вектора COM прерывания _SaveCom 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 _SaveCom ENDP ;======================================================= ; InstallCom: установить активный порт ; ; Возвращает в регистре ax - 1 при успешной установке ; и 0 в случае ошибки ; _InstallCom 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 _InstallCom ENDP ;======================================================= ; Восстановление векторов прерываний ; _RestoreCom 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 _RestoreCom 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 ; _OpenCom 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 _OpenCom ENDP ;======================================================= ; Запрещаем прерывания от асинхронного порта _CloseCom 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 _CloseCom ENDP ;======================================================= ; Снимаем сигнал DTR _DtrOff 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 _DtrOff ENDP ;======================================================= ; Устанавливаем сигнал DTR _DtrOn 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 _DtrOn ENDP ;======================================================= ; Возвращаем в регистре ax число байтов в регистре приемника, ; а в регистре dx общий размер буфера приемника _RCount 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 _RCount ENDP ;======================================================= ; Получаем очередной символ из буфера приемника, ; полученный символ удаляется из буфера _ReceiveCom 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 _ReceiveCom ENDP ;======================================================= ; Функция возвращает в регистре ax число свободных байт в ; буфере передатчика, а в регистре dx общий размер буфера ; передатчика _SCount 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 _SCount ENDP ;======================================================= ; Поместить символ в буфер передатчика ; [bp+6] - символ _SendCom 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 _SendCom ENDP ;======================================================= ; S_local ; _SendLocal 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 _SendLocal ENDP ;======================================================= ; Передаем удаленному модему сигнал "BREAK" ; _BreakCom 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 _BreakCom ENDP ;======================================================= ; Возвращаем в dx:ax указатель на счетчики ошибок ; _ComErrors 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 _ComErrors ENDP ;======================================================= ; Заполняем счетчики ошибок ; SetErr 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 SetErr ENDP ;======================================================= ; Протокол модема для передачи данных ; ModemProtocol 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 ModemProtocol 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 ; SAVE si mov si,OFFSET DGROUP:AREA3 jmp short handle_int ;======================================================= ; Обработчик прерываний от COM4 ; int_hndlr4 PROC FAR push si ; SAVE 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,6 je LSTAT_int ; Обнаружено состояние "BREAK" или произошла ошибка cmp al,0 je MSTAT_int ; Завершаем обработку прерываний jmp FAR PTR exit_handler LSTAT_int: ; Считываем регистр сотояния линии и вызываем функцию ; set_err, которая определит причину прерывания mov dx,LSR[si] in al,dx call SetErr 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 ModemProtocol ; Передаем очередной символ из буфера передатчика 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
Включаемый файл SENDCMD.H, представленный в листинге 6.11, содержит описания функций ExpectStr и SendStr. Эти функции были нами определены в модуле SENDCMD.C.
Листинг 6.11. Файл SENDCMD.H
unsigned ExpectStr(char *Search, unsigned int Timeout); void SendStr(char *str);
Включаемый файл MODEM.H (см. листинг 6.13) содержит описание функций Call, Answer, SlowWrite и ShutDown, определенных в модуле MODEM.C.
Листинг 6.12. Файл MODEM.H
int Call( void ); int Answer( void ); void SlowWrite( char *s, int len); void ShutDown( void ); extern char *device;
Включаемый файл MODEM.H (см. листинг 6.14) содержит описание функций Sleep и Delay, определенных в модуле TIMER.C, и предназначенных для организации временных задержек.
Листинг 6.13. Файл TIMER.H
void Sleep(time_t interval); void Delay(int milliseconds);
Включаемый файл TOOLS.H (см. листинг 6.15) содержит описание функций, определенных в модуле TOOLS.C.
Листинг 6.14. Файл 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(void); void CloseLine(void);
Включаемый файл UART.H (см. листинг 6.14) содержит описание функций, определенных в модуле UART.ASM и констант COM_E, используемых для определения причины ошибок COM-порта.
Листинг 6.15. Файл UART.H
void far SelectPort(int); void far SaveCom(void); void far RestoreCom(void); int far InstallCom(void); void far OpenCom( int, int, int, int ); void far CloseCom(void); void far DtrOff(void); void far DtrOn(void); long far RCount(void); // Макроопределение RCountSize возвращает // общий размер буфера приемника #define RCountSize() ((int)(RCount() >> 16)) // Макроопределение RCountPending возвращает // число байтов в буфере приемника #define RCountPending() ((int)RCount()) int far ReceiveCom(void); long far SCount(void); // Макроопределение SCountSize возвращает // общий размер буфера приемника #define SCountSize() ((int)(SCount() >> 16)) // Макроопределение SCountFree возвращает // число байтов в буфере приемника #define SCountFree() ((int)SCount()) void far SendCom(int); void far BreakCom(void); int far *far ComErrors(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
Название FOSSIL является набором первых символов из названий нескольких коммуникационных программ - "Fido/Opus/SEAdog Standard Layer". Эти программы используют FOSSIL-драйверы для работы с асинхронным последовательным адаптером.
FOSSIL-драйверы используются для расширения функций BIOS, обслуживающих асинхронный последовательный адаптер и модем. Кроме того, FOSSIL-драйверы поддерживают несколько функций для работы с клавиатурой, видеоадаптером и системным таймером.
Использование FOSSIL-драйверов позволяет увеличить скорость обмена через последовательный адаптер до 38400 бит/с (функции BIOS допускают максимальную скорость только 9600 бит/с).
FOSSIL-драйвер самостоятельно обрабатывает прерывания от COM-портов. Он содержит два внутренних буфера, организованных в виде очереди.
В первый буфер - буфер передатчика - записываются данные, передаваемые компьютером модему. Драйвер самостоятельно определяет, когда асинхронный адаптер способен передать внешнему устройству очередной символ (т. е. когда свободен регистр данных COM-порта) и записывает его в регистр данных COM-порта. При этом переданный символ удаляется из буфера и происходит передача следующего символа.
Во второй буфер - буфер приемника - драйвер записывает данные, поступающие в компьютер через COM-порт. Затем содержимое этого буфера может быть считано программой при помощи специальной функции драйвера.
Примером такого FOSSIL-драйвера может служить драйвер Gwinn's Communications Controller, X00.SYS Version V1.30. Вы можете получить любые FOSSIL-драйверы и документацию на них практически на каждой станции BBS.
Существуют специальные FOSSIL-драйверы, обеспечивающие программную эмуляцию аппаратных протоколов коррекции ошибок - от MNP2 до MNP5. Дополнительные функции, поддерживаемые этими драйверами, мы рассмотрим позже.
Интерфейс программ с FOSSIL-драйвером обеспечивается через прерывание INT_14h. При этом FOSSIL-драйвер подменяет встроенный обработчик прерывания INT 14h. FOSSIL-драйвер программирует асинхронный адаптер непосредственно через обращение к его регистрам.
Ниже мы приводим описание наиболее важных функций FOSSIL-драйверов пятого уровня.
Первая функция предназначена для инициализации портов асинхронного адаптера. Она задает формат и скорость передачи данных:
На входе: AH = 00h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; AL = параметры инициализации (см. ниже). На выходе: AX = состояние порта асинхронного адаптера, (см. функцию 03h).
При вызове этой функции регистр AL должен содержать параметры инициализации (x - состояние бита безразлично):
Биты |
Смысл |
D1 D0 |
Длина слова в битах:
00 - 5 бит;
01 - 6 бит;
10 - 7 бит;
11 - 8 бит |
D2 |
Количество стоповых бит:
0 - 1 бит;
1 - 2 бита |
D4 D3 |
Четность:
x0 - контроль на четность не используется;
01 - контроль на нечетность;
11 - контроль на четность |
D7-D5 |
Скорость передачи данных:
000 - 19200 бит/с
001 - 38400 бит/с
010 - 300 бит/с
011 - 600 бит/с
100 - 1200 бит/с
101 - 2400 бит/с
110 - 4800 бит/с
111 - 9600 бит/с |
Обратите внимание, что в отличие от функции BIOS, при задании скорости обмена (регистр AL биты D7, D6, D5) скорости в 110 и 150 бит/с заменены на 19200 и 38400 бит/с.
Основным достоинством FOSSIL-драйвера является буферизация передаваемых и принимаемых данных. При передаче байта он записывается программой в буфер драйвера, а затем передается драйвером в COM-порт. Для передачи используется следующая функция:
На входе: AH = 01h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; AL = передаваемый байт. На выходе: AX = состояние порта асинхронного адаптера (см. функцию 03h).
Если в буфере передатчика есть свободное место, то функция записывает передаваемый байт в буфер и возвращает управление не дожидаясь передачи байта в регистры последовательного адаптера. Если в буфере передатчика нет свободного места, функция будет ожидать, пока в буфере передатчика не освободится место для передаваемого байта.
Функция 02h предназначена для чтения очередного символа из буфера приемника драйвера.
На входе: AH = 02h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: AL = принятый байт; AH = 0.
Если буфер приемника пуст, функция ожидает поступления очередного байта из COM-порта.
Состояние порта асинхронного адаптера можно узнать с помощью функции 03h:
На входе: AH = 03h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: AH = состояние буферов драйвера; D0 - принятые драйвером символы доступны для чтения; D1 - приемный буфер драйвера переполнен, все символы, полученные после переполнения буфера, будут потеряны; D5 - в буфере передатчика есть свободное место; D6 - буфер передатчика пуст; AL = состояние линии DCD; D3 = 1; D7 - состояние сигнала DCD.
Данная функция используется для инициализации FOSSIL-драйвера. Эта функция должна быть вызвана перед вызовом других функций драйвера.
На входе: AH = 04h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; если BX = 4F50h ES:CX - указатель на флаг <Ctrl-C>. На выходе: AX = 1954h; BL = максимальный номер функции (регистр AH при вызове прерывания INT 14h), поддерживаемой данным драйвером, не считая функций с номерами, большими 7Dh; BH = уровень драйвера.
Если при вызове данной функции регистр BX равен 4F50h, то регистры ES:CX указывают на однобайтный счетчик, содержимое которого увеличивается при нажатии комбинации клавиш <Ctrl-C>.
При инициализации драйвера происходит установка сигнала DTR. Для сброса драйвера (очистки буферов, сброса флага управления потока и т. д.) необходимо вызвать эту функцию второй раз. Данную функцию можно использовать для проверки, установлен ли FOSSIL-драйвер.
Эта функция сообщает драйверу, что программа закончила работу с последовательным адаптером.
На входе: AH = 05h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: не используется.
Функция 06h используется для управления линией DTR. Заметим, что на состояние линии DTR кроме этой функции влияет только функция инициализации FOSSIL-драйвера.
На входе: AH = 06h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; AL = состояние линии DTR: 01h - установить сигнал DTR; 00h - сбросить сигнал DTR. На выходе: не используется.
Данная функция используется для определения параметров системного таймера
На входе: AH = 07h. На выходе: AL = номер прерывания от системного таймера; AH = количество прерываний от системного таймера на секунду; DX = интервал между прерываниями от системного таймера, определяется в миллисекундах
Данная функция используется для ускорения процесса передачи в COM-порт данных из буфера передатчика драйвера. Функция не возвращает управление до тех пор, пока буфер передатчика не станет пустым.
На входе: AH = 08h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: не используется.
Функция используется для очистки буфера передатчика. Все данные, находящиеся на момент вызова функции в буфере, пропадают и в COM-порт не передаются.
На входе: AH = 09h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: не используется.
Функция используется для очистки приемного буфера драйвера. Все данные, находящиеся на момент вызова функции в буфере, пропадают.
На входе: AH = 0Ah; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: не используется.
Если в буфере передатчика есть свободное место, то функция записывает передаваемый байт в буфер и возвращает в регистре AX значение 01h. Если в буфере передатчика нет свободного места, функция записывает передаваемый байт в буфер и возвращает в регистре AX значение 00h.
На входе: AH = 0Bh; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; AL = передаваемый байт. На выходе: AX = 0001h - символ размещен в буфере передатчика; AX = 0000h - символ не размещен в буфере передатчика.
Функция 0Ch предназначена для чтения очередного символа из буфера приемника драйвера. При этом прочитанный символ из буфера не удаляется.
На входе: AH = 0Ch; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: если AH = 0, то регистр AL содержит принятый байт; если AX = 0FFFFh, то буфер приемника пуст.
Если буфер приемника пуст, функция ожидает поступления очередного байта из COM-порта.
Данная функция обеспечивает ввод с клавиатуры без ожидания. Если буфер клавиатуры пуст - функция возвращает в регистре AX значение 0FFFFh. В противном случае скан-код очередного символа, прочитанный из буфера клавиатуры, помещается в регистр AH. Заметим, что функция не удаляет код прочитанного ей символа из буфера клавиатуры.
На входе: AH = 0Dh. На выходе: AX = 0FFFFh - буфер клавиатуры пуст; AX = скан-код нажатой клавиши.
Функция 0Eh производит чтение кода очередного символа из буфера клавиатуры. Если буфер клавиатуры пуст, функция переходит в режим ожидания до тех пор, пока не будет нажата какая-нибудь клавиша.
На входе: AH = 0Eh. На выходе: AX = скан-код нажатой клавиши.
При связи двух устройств, работающих с различными скоростями, используют механизм управления потоком. Он подразумевает, что приемное устройство, не справляющееся с обработкой поступающих ему данных, подает передающему устройству определенный сигнал. При поступлении в передающее устройство данного сигнала оно приостанавливает передачу и ожидает, пока приемное устройство не обработает принятые данные и не подаст сигнал, разрешающий возобновить передачу данных.
На входе: AH = 0Fh; AL = способ управления потоком: D0 Использование для управления передачей символов XON/XOFF D1 Использование для управления потоком сигналов CTS/RTS D2 Зарезервирован D3 Использование для управления приемом символов XON/XOFF D7-D4 Зарезервированы DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: не используется.
Вы можете выбрать тот или иной метод управления потоком, установив соответствующий бит регистра AL:
На входе: AH = 10h; AL = способ управления потоком: D0 - Если данный бит равен единице, то полученные через COM-порт символы Ctrl-C/K не записываются в буфер приемника, а устанавливают внутренний флаг. Следующий вызов функции будет возвращать в регистре AX значение этого флага. D1 - Если данный бит равен единице, процесс передачи данных драйвером останавливается. Если D1 равен нулю, передача возобновляется. DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д. На выходе: AX = 01h - были получены символы Ctrl-C/K; AX = 01h - символы Ctrl-C/K не были получены.
Функция используется для установки курсора в заданное положение экрана. Новое положение курсора определяется регистром DX.
На входе: AH = 11h; DL = номер столбца; DH = номер строки. На выходе: не используется.
Заметим, что данная функция аналогична функции 02h прерывания INT 10h.
Функция используется для определения текущего положения курсора на экране.
На входе: AH = 12h. На выходе: DL = номер столбца; DH = номер строки.
Данная функция аналогична функции 03h прерывания INT_10h.
Эту функцию можно использовать для вывода символа на экран дисплея в режиме ANSI. В отличие от функций DOS данная функция является реентерабельной.
На входе: AH = 13h; AL = код отображаемого символа. На выходе: не используется.
Если производится отслеживание сигнала DCD и сигнал DCD становится неактивным, то FOSSIL-драйвер производит перезагрузку системы.
На входе: AH = 14h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; AL = 01h - производится отслеживание сигнала DCD; AL = 00h - отслеживание сигнала DCD не производится. На выходе: не используется.
Эта функция производит вывод символа на экран дисплея. Для вывода на экран символа данная функция использует процедуры BIOS.
На входе: AH = 15h; AL = код отображаемого символа. На выходе: не используется.
Позволяет установить вектор прерываний от системного таймера на данную функцию. В этом случае функция будет вызываться всякий раз, когда приходит прерывание от системного таймера. Можно установить несколько функций для вызова их по прерываниям таймера. При этом они образуют цепочку и вызываются последовательно одна за другой.
На входе: AH = 16h; AL = 01h - добавить функцию; AL = 00h - удалить функцию; ES:DX - адрес функции. На выходе: AX = 00000h - операция выполнена успешно; AX = 0FFFFh - произошла ошибка.
Производит перезагрузку системы.
На входе: AH = 17h.
Позволяет за один вызов функции считать сразу несколько байт из приемного буфера драйвера в буфер программы.
На входе: AH = 18h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; CX = максимальное количество считываемых символов; ES:DI - адрес буфера, в который помещаются считанные символы. На выходе: AX = количество считанных символов.
Позволяет за один вызов функции записать несколько байт из буфера программы в буфер передатчика драйвера.
На входе: AH = 19h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; CX = максимальное количество считываемых символов; ES:DI - адрес буфера, в который помещаются считанные символы. На выходе: AX = количество записанных символов.
Используя данную функцию, можно перевести телефонную линию в состояние BREAK.
На входе: AH = 1Ah; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; AX = 01h - начало передачи сигнала BREAK; AX = 00h - конец передачи сигнала BREAK. На выходе: не используется.
С помощью данной функции вы можете получить информацию о FOSSIL-драйвере.
На входе: AH = 1Bh; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4 и т. д.; ES:DI - адрес буфера, в который помещается информация о драйвере; CX = размер буфера в байтах; На выходе: AX = количество байтов, записанных в буфер.
Функция производит запись информации о FOSSIL-драйвере в буфер программы. Формат буфера представлен ниже:
Смещение |
Размер |
Смысл |
0 |
Слово |
Размер заполненной части буфера (размер этой таблицы в байтах
- 13h) |
2 |
Байт |
Номер версии драйвера |
3 |
Байт |
Уровень драйвера |
4 |
Двойное слово |
Указатель на символьную строку с идентификатором драйвера |
8 |
Слово |
Размер буфера приемника |
0Ah |
Слово |
Количество свободных байт в буфере приемника |
0Ch |
Слово |
Размер буфера передатчика |
0Eh |
Слово |
Количество свободных байт в буфере передатчика |
10h |
Байт |
Ширина экрана в символах |
11h |
Байт |
Высота экрана в символах |
12h |
Байт |
Скорость обмена данными (см. функцию установки скорости
передачи данных, регистр AL) |
Данная функция позволяет установить внешние (по отношению к FOSSIL-драйверу) функции. Номер устанавливаемой функции может быть от 80h до 0BFh. После успешной установки функции она может быть вызвана как соответствующая функция прерывания INT 14h.
На входе: AH = 7Eh; AL = номер устанавливаемой функции (80h-0BFh); ES:DX - адрес точки входа функции. На выходе: AX = 1954h; BL = номер, присвоенный функции, соответствует регистру AL; BH = 01h - установка функции прошла успешно; BH = 00h - произошла ошибка.
Функция 7Fh используется для отключения внешней функции, установленной при помощи функции 7Eh.
На входе: AH = 7Fh; AL = номер, присваиваемый функции; ES:DX - адрес точки входа функции. На выходе: AX = 1954h; BL = номер, присвоенный функции; BH = 01h - удаление функции прошло успешно; BH = 00h - произошла ошибка.
Кроме буферизации передаваемых и принимаемых данных некоторые FOSSIL-драйверы выполняют еще одну функцию. При работе с модемами, не реализующими протокол аппаратной коррекции ошибок, они обеспечивают программную эмуляцию протокола MNP.
Примером таких драйверов могут служить FOSSIL-драйверы MX5 Version 1.02 - MNP Level 5 Driver и MNP Version 1.27 - MNP Level 5 Driver.
Для управления эмулятором MNP FOSSIL-драйверы, реализующие программную эмуляцию, поддерживают дополнительную функцию номер 0E0h прерывания INT 14h. Ниже мы приведем краткое описание подфункций данной функции.
На входе: AH = 0E0h; AL = 00h; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4. На выходе: ES:BX - указатель на структуру, содержащую информацию о текущем состоянии эмулятора MNP.
При помощи данной подфункции можно определить состояние эмулятора MNP. Подфункция возвращает в регистрах ES:BX указатель на следующую структуру:
Смещение |
Размер |
Смысл |
0 |
Байт |
Если байт содержит 0 - эмуляция MNP включена, 1 - эмуляция MNP
отключена |
1 |
Байт |
Уровень эмулируемого протокола MNP (2, 4, 5) |
2 |
Байт |
Серийный номер MNP удаленного модема |
3 |
Слово |
Общее число переданных пакетов |
5 |
Слово |
Число повторно переданных пакетов |
7 |
Слово |
Максимальная скорость передачи |
9 |
Слово |
Общее число принятых пакетов |
11 |
Слово |
Число повторно принятых пакетов |
13 |
Слово |
Максимальная скорость приема |
На входе: AH = 0E0h; AL = 01h; BH = 00h - определить текущий уровень MNP; = 01h - установить уровень MNP; BL - Уровень протокола MNP; = 02h - уровень MNP2, = 04h - уровень MNP4, = 05h - уровень MNP5 (устанавливается по умолчанию); DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4. На выходе: BL = уровень эмулируемого протокола MNP.
Эмулятор MNP может работать в двух режимах - режиме вызова удаленного модема и режиме ответа на вызов от удаленного модема. Данная подфункция позволяет определить текущий и установить нужный режим работы эмулятора.
На входе: AH = 0E0h; AL = 02h; BH = 00h - определить текущий режим; = 01h - установить режим; BL = 00h - режим вызова (устанавливается по умолчанию), = 01h - режим ответа; DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4. На выходе: BL = текущий режим эмулятора.
Данная подфункция используется для определения и задания времени после установления соединения, в течение которого драйвер пытается установить с удаленным модемом связь с использованием MNP. Время задается в 1/55 долях миллисекунды или, другими словами, количеством прерываний от системного таймера, которые должны произойти за данный промежуток времени.
На входе: AH = 0E0h; AL = 03h; BH = 00h - определить интервал времени; = 01h - установить интервал времени; BL = интервал времени в 1/55 долях миллисекунды (по умолчанию устанавливается 14); DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4. На выходе: BL = интервал времени.
Данная подфункция определяет звуковой режим эмулятора MNP. Если звук включен, то после соединения с удаленным модемом FOSSIL-драйвер будет генерировать на динамике компьютера различные звуковые сигналы в зависимости от того, в каком режиме произошло соединение. При соединении с эмуляцией MNP производятся три гудка, а при соединении без эмуляции MNP - только один.
На входе: AH = 0E0h; AL = 04h; BH = 00h - определить звуковой режим, = 01h - установить звуковой режим; BL = 00h - звук не включен, = 01h - звук включен (устанавливается по умолчанию); DX = номер порта: 0 - COM1, 1 - COM2, 2 - COM3, 3 - COM4. На выходе: BL = текущий звуковой режим.
Данную подфункцию можно использовать для удаления FOSSIL-драйвера из оперативной памяти компьютера. При этом драйвер освобождает телефонную линию, восстанавливает все перехваченные им векторы прерываний и возвращает адрес своего блока MCB. Далее вы можете воспользоваться функцией 49h прерывания INT 21h для освобождения этого MCB.
На входе: AH = 0E0h; AL = 05h. На выходе: BX = адрес MCB или 0 в случае ошибки.
Приведем пример программы UNINST, удаляющей FOSSIL-драйвер из памяти. Исходный текст этой программы представлен в листинге 6.16.
Листинг 6.16. Файл UNINST.C
int uninstall(void); //******************************************************** // Функция main //******************************************************** void main(void) { int ok; ok = uninstall(); printf("Удаление FOSSIL-драйвера из памяти %s.", (ok) ? "прошло успешно" : "невозможно" ); } //******************************************************** // Функция uninstall //******************************************************** int uninstall(void) { int ok = 0; asm { // Определяем адрес MCB блока драйвера mov ax,0E005h int 14h // В случае ошибки возвращаем управление cmp bx,0 je no_uninstall // es = bx push bx pop es // Освобождаем MCB блок, используемый драйвером mov ah,49h int 21h mov ok,1 } no_uninstall: return(ok); }
На входе: AH = 0E0h; AL = 06h; BX = 00h. На выходе: BX = 4D58h; AH = старшая часть номера версии эмулятора; AL = младшая часть номера версии.
Данная подфункция позволяет выполнить временную задержку. Время задержки определяется количеством прерываний от системного таймера, которые должны произойти во время задержки.
На входе: AH = 0E0h; AL = 07h; СX = время задержки. На выходе: не используется.
Ниже представлен исходный текст небольшой коммуникационной программы FOSSILEX, использующей для работы с модемом FOSSIL-драйвер.
При запуске программа проверяет наличие FOSSIL-драйвера и, если он не установлен, сообщает об этом и завершает свою работу.
Если FOSSIL-драйвер установлен, программа инициализирует его и устанавливает скорость обмена 2400 бит/с. Затем программа проверяет, поддерживает ли установленный FOSSIL-драйвер эмуляцию протокола MNP, и выводит на экран соответствующее сообщение.
Затем начинает выполняться цикл, в котором программа отображает принятые от модема данные на экране и посылает модему ASCII-коды символов, набранных на клавиатуре.
Если FOSSIL-драйвер поддерживает эмуляцию протокола MNP, то, нажав клавиши PageUp или PageDown, можно переключить эмулятор MNP либо в режим вызова удаленного модема, либо в режим ответа на вызов удаленного модема.
Исходный текст программы представлен в листинге 6.17.
Листинг 6.17. Файл FOSSILEX.C
#include <stdio.h> #include <conio.h> void do_chat(void); void help(void); void origin(void); void answer(void); unsigned com_port_num; unsigned char mnp = 0; //******************************************************** // Функция main //******************************************************** void main( int argc, char *argv[] ) { unsigned key, f_num, doc = 0, signatura; printf("\n(С) Frolov G.V. Коммуникационная программа, " "использующая FOSSIL-драйвер\n"); // Параметр программы должен содержать номер // используемого COM-порта if( argc < 2 ) help(); com_port_num = ( unsigned ) atoi( argv[1] ); // Инициализируем FOSSIL-драйвер _asm { mov ah,4h mov dx,com_port_num int 14h mov key,ax mov f_num,bl mov doc,bh } // Если FOSSIL-драйвер не установлен завершаем программу if( key != 0x1954 ) { printf("\nFOSSIL-драйвер неустановлен\n"); exit(-1); } // Определяем возможность эмуляции FOSSIL-драйвером // протокола MNP _asm { mov ah,0E0h mov al,6h xor bx,bx int 14h mov signatura,bx } mnp = ( signatura != 0x4D58 ) ? 0 : 1; printf("Эмуляция MNP %s.\n\n", (mnp) ? "поддерживается" : "не поддерживается"); if(mnp) printf("PageUp - режим вызова, " "PageDown - режим ответа\n\n"); _asm { // Устанавливаем скорость обмена и формат данных xor ah,ah mov al,0A3h mov dx,com_port_num int 14h // Устанавливаем сигнал DTR в активное состояние mov ah,6 mov al,1 mov dx,com_port_num int 14h // Запрещаем использование режима управления потоком mov ah,0Fh xor al,al mov dx,com_port_num int 14h } // Начинаем обмен данными между компьютером и модемом do_chat(); } //******************************************************** // Функция do_chat //******************************************************** void do_chat( void ) { while(1) { unsigned char key,stey; unsigned i,j; // Если пользователь нажал на клавиатуру, получаем код // нажатого символа и передаем его модему if( kbhit() ) { key = getch(); if(( key == 0 ) && mnp) { key = getch(); if( key == 73 ) origin(); else if( key == 81 ) answer(); continue; } // По нажатию клавиши Esc выходим из программы if( key == 27 ) { _asm { // Сбрасываем сигнал DTR mov ah,06h xor al,al mov dx,com_port_num int 14h // Отключаем FOSSIL-драйвер mov ah,05h mov dx,com_port_num int 14h } return; } // Если нажата клавиша Enter переводим строку if( key == '\r' ) { key = 13; putch('\n'); } // Выводим ASCII код нажатого на клавиатуре символа // на экран putch(key); // Передаем ASCII код нажатого на клавиатуре символа // FOSSIL-драйверу, для дальнейшей передачи его модему _asm { mov dx,com_port_num mov ah,1 mov al,key int 14h } } // Определяем состояние приемного буфера FOSSIL-драйвера _asm { mov dx,com_port_num mov ah,3 int 14h and ah,01h mov stey,ah } // Если приемный буфер содержит данные, выводим их // на экран дисплея if( stey > 0 ) { _asm { mov dx,com_port_num mov ah,2 int 14h mov stey,al } putch(stey); } } } //******************************************************** // Функция help //******************************************************** void help( void ) { printf("Неправильно задан параметр программы \n" "FOSSILEX n, где n - номер порта от 0 до 3\n"); exit(0); } //******************************************************** // Функция origin //******************************************************** void origin(void) { // Переключаем эмулятор MNP в режим вызова удаленного модема _asm { mov ah,0E0h mov al, 2 mov bl,0 mov bh,1 mov dx,com_port_num int 14h } putch(7); } //******************************************************** // Функция answer //******************************************************** void answer(void){ // Переключаем эмулятор MNP в режим ответа на // вызов удаленного модема _asm { mov ah,0E0h mov al,2 mov bl,1 mov bh,1 mov dx,com_port_num int 14h } putch(7); putch(7); }