Модемы и факс-модемы. Программирование для MS-DOS и Windows

         

Подтверждение связи


Рассмотрим процесс подтверждения связи между компьютером и модемом (handshake). В начале сеанса связи компьютер должен удостовериться, что модем может произвести вызов (находится в рабочем состоянии). Затем после вызова абонента модем должен сообщить компьютеру, что он соединился с удаленной системой. Подробнее это происходит следующим образом.

Компьютер устанавливает сигнал на линии DTR, чтобы показать модему, что он готов к проведению сеанса связи. В ответ модем устанавливает сигнал на линии DSR. Когда модем произвел соединение с другим удаленным модемом, он устанавливает сигнал на линии DCD, чтобы сообщить об этом компьютеру.

Падение напряжения на линии DTR сообщает модему, что компьютер не может далее продолжать сеанс связи, например, из-за того, что выключено питание компьютера. В этом случае модем прервет связь. Падение напряжения на линии DCD сообщает компьютеру, что модем потерял связь и не может больше продолжать соединение с удаленным модемом.



Поле BaudRate


Поле BaudRate определяет скорость передачи, с которой работает COM-порт. Скорость можно задать двумя способами. Вы можете записать в это поле либо одну из констант CBR_, определенных в файле WINDOWS.H, либо абсолютное значение скорости передачи информации.

Список констант CBR_ и соответствующие им значения скорости перечислены в представленной ниже таблице.

Константа

Значение

Скорость передачи информации, бит/с

CBR_110



0xFF10

110

CBR_300

0xFF11

300

CBR_600

0xFF12

600

CBR_1200

0xFF13

1200

CBR_2400

0xFF14

2400

CBR_4800

0xFF15

4800

CBR_9600

0xFF16

9600

CBR_14400

0xFF17

14400

CBR_19200

0xFF18

19200

CBR_38400

0xFF1B

38400

CBR_56000

0xFF1F

56000

CBR_128000

0xFF23

128000

CBR_256000

0xFF27

256000

Как видите, старший байт для констант CBR_ содержит значение 0xFF. Поэтому, если вы желаете записать в поле BaudRate абсолютное значение скорости, указывайте числа, меньшие, чем значение константы CBR_110 (0xFF10), и большие 2.

Если вы запишите в поле BaudRate неправильное значение, функция SetCommState завершится с ошибкой IE_BAUDRATE.



Поле ByteSize


В поле ByteSize определяется количество бит в символах, передаваемых и принимаемых через COM-порт. Это поле может содержать любое значение от 4 до 8. Если вы запишете в него другое значение, то функция SetCommState завершится с ошибкой IE_BYTESIZE.



Поле CtsTimeout


Поле CtsTimeout определяет максимальный интервал времени (тайм-аут), в течение которого функция WriteComm будет ожидать появления сигнала CTS перед тем, как записать данные в очередь передатчика. Интервал времени задается в миллисекундах.

Вы можете записать в эти поля константы INFINITE или IGNORE. Назначение этих констант уже было рассмотрено нами при описании поля RlsTimeout.

Если интервал ожидания истечет, а сигнал CTS не установится, функция WriteComm вернет нулевое значение. Чтобы узнать причину ошибки, вызовите функцию GetCommError. Если функция WriteComm возвратила управление, после того как истек тайм-аут, GetCommError возвратит значение CE_CTSTO.



Поле DsrTimeout


Поле DsrTimeout определяет максимальный интервал времени (тайм-аут), в течение которого функция WriteComm будет ожидать появления сигнала DSR перед тем, как записать данные в очередь передатчика. Интервал времени задается в миллисекундах.

Вы можете записать в эти поля константы INFINITE или IGNORE. Назначение этих констант было уже рассмотрено при описании поля RlsTimeout.

Если интервал ожидания истечет, а сигнал DSR не установится, функция WriteComm вернет нулевое значение. Чтобы узнать причину ошибки, вызовите функцию GetCommError. Если функция WriteComm возвратила управление, после того как истек тайм-аут, GetCommError возвратит значение CE_DSRTO.



Поле EofChar


Поле EofChar определяет символ EOF, используемый при передаче сигнала "конец данных".

Функция BuildCommDCB записывает в это поле значение 0x0. Смотри поле fBinary.



Поле EvtChar


Поле EvtChar определяет символ, используемый для передачи сигнала событие (EV_RXFLAG). Когда принимается символ EvtChar, генерируется событие EV_RXFLAG. В действительности флаг fChEvt не используется. Необходимо вызвать функцию SetCommEventMask, чтобы разрешить это событие.

Функция BuildCommDCB записывает в это поле значение 0x0.



Поле fBinary


Поле fBinary, которое используется как флаг, устанавливает режим передачи двоичной информации. Если это поле содержит значение FALSE (ноль), принятый символ EOF распознается как сигнал для окончания приема данных. Символ EOF определяется полем EofChar структуры DCB.

После того как функция ReadComm прочтет символ EOF из входной очереди, она возвращает управление, не прочитав остальные данные. Если во входном буфере остались непрочитанные данные, они распознаются как переполнение. В этом случае функция GetCommError вернет код ошибки CE_RXOVER.

При получении символа EOF в структуре COMSTAT устанавливается флаг CSTF_EOF.

Если в поле fBinary записано значение TRUE (единица), символ EOF обрабатывается так же, как и остальные символы. Это позволяет беспрепятственно передавать через COM-порт не только текстовые, но также и двоичные данные.



Поле fChEvt


При установке поля fChEvt, получение символа EvtChar отмечается как событие EV_RXFLAG. В действительности поле fChEvt не используется. Необходимо вызвать функцию SetCommEventMask, чтобы разрешить это событие.



Поле fDtrDisable


Поле fDtrDisable определяет, будет ли использоваться сигнал DTR. Если это флаг установлен, сигнал DTR не используется и остается равен нулю. Если поле fDtrDisable содержит нулевое значение (FALSE), сигнал DTR устанавливается, когда COM-порт открыт, и сбрасывается, когда порт закрывается.

Обычно поле fDtrDisable используют совместно с fDtrFlow. Их использование аналогично полям fRtsDisable и fRtsFlow.



Поле fDtrflow


Поле fDtrflow определяет, что сигнал DTR используется для протокола управления потоком (при приеме данных). Если этот флаг установлен, сигнал DTR выключается, когда в приемной очереди становится больше чем XoffLim символов, и включается снова, когда в приемной очереди становится меньше, чем XonLim символов.

Такой механизм позволяет предотвратить переполнение входной очереди COM-порта, которое может вызвать потерю принятых данных.



Поле fInX


Поле fInX устанавливает протокол управления потоком XON/XOFF при приеме данных. Если это поле установлено, то символ XoffChar передается, когда в очереди приемника становится больше, чем XoffLim символов и символ XonChar, когда в очереди приемника становится меньше, чем XonLim символов.

Если передача данных прерывается в результате передачи символа XOFF, в структуре COMSTAT устанавливается флаг CSTF_XOFFSENT.



Поле fNull


Если поле fNull содержит значение TRUE, все принятые из COM-порта символы, имеющие нулевое значение, будут пропускаться.



Поле fOutX


Поле fOutX включает протокол управления потоком XON/XOFF. Если это поле установлено, передача информации приостанавливается, когда принимается символ XoffChar, и возобновляется при получении сигнала XonChar.

Если передача данных прерывается в результате приема символа XOFF, в структуре COMSTAT устанавливается флаг CSTF_XOFFHOLD.



Поле fOutxCtsFlow


Поле fOutxCtsFlow определяет, что сигнал CTS используется для управления выходным потоком данных. Если это поле содержит значение TRUE и сигнал CTS выключен, передача информации приостанавливается до тех пор, пока сигнал CTS не установится снова.

Если поле CtsTimeout содержит величину, отличную от нуля, функция WriteComm будет ожидать появления сигнала CTS вне зависимости от значения поля fOutxCtsFlow.



Поле fOutxDsrFlow


Поле fOutxDsrFlow определяет, что сигнал DSR используется для управления выходным потоком. Если это поле содержит значение TRUE и сигнал DSR выключен, передача информации приостанавливается до тех пор, пока сигнал CTS снова не установится.

Если поле DsrTimeout содержит величину, отличную от нуля, функция WriteComm будет ожидать появления сигнала DSR вне зависимости от значения поля fOutxDsrFlow.



Поле fParity


Поле fParity определяет, будет ли выполняться проверка принимаемой и передаваемой информации на четность. Если в этом поле записано значение TRUE, проверка на четность выполняется.

Если функция ReadComm обнаружит ошибку четности, она возвращает отрицательную величину. В этом случае вы должны воспользоваться функцией GetCommError, чтобы определить причину и сбросить флаг ошибки CE_RXPARITY.

Чтобы при обнаружении ошибки по четности в функцию окна поступало сообщение EV_ERR, воспользуйтесь функцией SetCommEventMask.



Поле fPeChar


Если поле fPeChar содержит значение TRUE, каждый символ, полученный с ошибкой по четности, будет заменен на символ, заданный полем PeChar.



Поле fRtsDisable


Поле fRtsDisable определяет, будет ли использоваться сигнал RTS. Если это флаг установлен, сигнал RTS не используется и все время остается равен нулю. Если поле fRtsDisable содержит нулевое значение (FALSE), сигнал RTS устанавливается, когда COM-порт открывается и сбрасывается, когда порт закрывается.

Чтобы использовать сигнал RTS для управления потоком, необходимо записать в поле fRtsDisable нулевое значение, а в поле fRtsFlow - единицу. Другие комбинации значений fRtsDisable и fRtsFlow блокируют управление потоком по линии RTS.

Ниже перечислены различные комбинации полей fRtsDisable и fRtsFlow:

Поле fRtsDisable

Поле fRtsFlow

Состояние сигнала RTS

0

0

Установлен

0

1

Разрешено управление потоком по линии RTS

1

0

Сброшен

1

1

Сброшен

Если сигналы RTS и DTR не используются для управления потоком, их можно устанавливать и сбрасывать с помощью функции EscapeCommFuntion.



Поле fRtsflow


Поле fRtsflow определяет, что сигнал RTS используется для управления потоком (при приеме данных). Если это поле установлено, сигнал RTS выключается, когда в приемной очереди становится больше чем XoffLim символов и включается, когда в приемной очереди становится меньше, чем XonLim символов.



Поле Id


Содержит идентификатор COM-порта. Значение поля устанавливается функцией BuildCommDCB, описанной ниже, и содержит то же самое значение, которое возвращает функция OpenComm при открытии этого порта.

Если старший бит поля Id установлен, данная структура DCB описывает порт параллельного адаптера.



Поле Parity


Поле Parity управляет контролем четности и может содержать одну из пяти констант, перечисленных ниже:

Значение

Смысл

EVENPARITY

Выполняется проверка на четность

MARKPARITY

Бит четности всегда установлен

NOPARITY

Проверка на четность не выполняется

ODDPARITY

Выполняется проверка на нечетность

SPACEPARITY

Бит четности всегда сброшен

Если записать в это поле другое значение, функция SetCommState вернет код ошибки IE_DEFAULT.



Поле PeChar


Поле PeChar задает значение символа, используемого для замещения символов, принятых с ошибкой по четности.

Функция BuildCommDCB записывает в это поле значение 0x0. Смотри поле fPeChar и fParity.



Поле RlsTimeout


Поле RlsTimeout определяет максимальный интервал времени (тайм-аут), в течение которого функция WriteComm будет ожидать появления сигнала RLSD (ранее обозначался нами как DCD) перед тем, как записать данные в очередь передатчика. Интервал времени задается в миллисекундах.

Вы можете записать в эти поля константы INFINITE или IGNORE, определенные в файле WINDOWS.H как 0xFFFF и 0x0, соответственно.

INFINITE можно перевести как бесконечный, однако это не означает, что функция WriteComm будет бесконечно долго ждать сигнал RLSD. На самом деле эта константа задает интервал времени примерно 65 секунд.

IGNORE означает, что WriteComm не ожидает сигнал RLSD.

Если интервал ожидания истечет, а сигнал RLSD так и не установится, функция WriteComm вернет нулевое значение. Чтобы узнать причину ошибки, вызовите функцию GetCommError. Если функция WriteComm возвратила управление, после того как истек тайм-аут, GetCommError возвратит значение CE_RLSDTO.



Поле StopBits


Поле StopBits задает количество стоповых бит. Поле может содержать одну из констант, перечисленных ниже:

Значение

Смысл

ONESTOPBIT

1 стоповый бит

ONE5STOPBITS

1.5 стоповых бита

TWOSTOPBITS

2 стоповых бита

Если записать в это поле другое значение, то функция SetCommState вернет код ошибки IE_DEFAULT.



Поле TxDelay


Поле TxDelay не используется в Windows 3.1 и Windows for Workgroups 3.11.



Поле XoffChar


Поле XoffChar определяет значение символа XOFF для передачи и для приема. Смотри описание полей fInX и fOutX.

Функция BuildCommDCB, описанная ниже, записывает в это поле значение 0x13 (<CTRL+S>).

Если вы решили изменить принятые по умолчанию значения полей XonChar и XoffChar, следует обратить внимание на то, что они должны содержать различные величины. В противном случае управление потоком при помощи символов XON/XOFF будет работать неправильно и обмен данными может прекратиться.



Поле XoffLim


Поле XoffLim определяет максимально допустимое число символов в приемной очереди, при превышении которого передается символ XOFF (если включен протокол) и сбрасывается сигнал DTR (если включен протокол).

Чтобы определить максимально допустимое количество символов в очереди, надо вычесть из размера очереди приемника значение поля XoffLim.

Функция BuildCommDCB записывает в это поле значение 0x10. Для изменения значения, записанного в поле, можно воспользоваться функцией SetCommState.



Поле XonChar


Поле XonChar содержит значение символа XON для передачи и для приема. Смотри описание полей fInX и fOutX.

Функция BuildCommDCB записывает в это поле значение 0x11 (<CTRL+Q>).



Поле XonLim


Поле XonLim определяет минимально допустимое число символов в приемной очереди, при принижении которого передается символ XON (если включен протокол XON/XOFF) и устанавливается сигнал DTR (если включен протокол DTR).

Функция BuildCommDCB записывает в это поле значение 0x10.



Получение информации о драйвере


С помощью данной функции вы можете получить информацию о 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)



Порты асинхронного адаптера


На этапе инициализации системы, модуль процедуры начальной загрузки BIOS тестирует имеющиеся асинхронные порты RS-232-C и инициализирует их. В зависимости от версии BIOS инициализирует первые два или четыре порта. Их базовые адреса записываются в области данных BIOS начиная с адреса 0040:0000h.

Чтобы просмотреть значения, записанные в области данных BIOS вашего компьютера, можно воспользоваться программой Debug, поставляемой вместе с операционной системой MS-DOS. Таким образом, можно определить, какие COM-порты установлены на компьютере.

Запустите программу Debug. Для этого введите в строке системного приглашения MS-DOS команду DEBUG.EXE. Программа Debug выведет на экран приглашение в виде черточки '-'. Чтобы просмотреть содержимое оперативной памяти по адресу 0040:0000h введите команду d40:0 и нажмите клавишу <Enter>. На экране появится дамп памяти, начиная с адреса 0040:0000h до 0040:0080h.

-d40:0

0040:0000  F8 03 F8 02 E8 03 00 00-78 03 00 00 00 00 00 00   ........x.......

0040:0010  61 C6 00 80 02 80 00 20-00 00 38 00 38 00 E0 50   a...... ..8.8..P

Нас будут интересовать только первые восемь байт из этого дампа. Первые два байта содержат базовый адрес порта COM1. Поменяв местами два этих байта, получаем адрес 3F8h. Следующие два байта содержат адрес порта COM2 - 2F8h, затем COM3 - 3E8h. Два байта, соответствующие порту COM4, содержат нулевые значения. Это означает, что асинхронный последовательный адаптер компьютера не имеет порта COM4 или BIOS компьютера не может или не пытается его обнаружить.

Теперь вы можете завершить работу программы Debug. Для этого введите команду q и нажмите клавишу <Enter>.

Адреса COM-портов из нашего примера являются своего рода стандартом. Первый адаптер COM1 обычно имеет базовый адрес 3F8h и занимает диапазон адресов от 3F8h до 3FFh. Второй адаптер COM2 имеет базовый адрес 2F8h и занимает адреса 2F8h...2FFh. Третий адаптер COM3 имеет базовый адрес 3E8h и занимает диапазон адресов от 3E8h до 3EFh. Четвертый адаптер COM4 имеет базовый адрес 2E8h и занимает адреса 2E8h...2EFh. Тем не менее, для некоторых компьютеров, например, с шиной MCA - PS/2, адреса COM-портов могут иметь другие значения.

Порты асинхронного адаптера могут вырабатывать прерывания:

 COM1, COM3 - IRQ4 (соответствует INT 0Ch);

 COM2, COM4 - IRQ3 (соответствует INT 0Bh).

Порты COM1 и COM3 асинхронного последовательного адаптера используют линию IRQ4 и вырабатывают прерывание INT 0Ch, а порты COM2 и COM4 используют линию IRQ3 и вырабатывают прерывание INT 0Bh.

Некоторые платы последовательного асинхронного адаптера позволяют присвоить COM-портам другие линии IRQ, например, IRQ5 или IRQ7.

        Как видно, порты COM1, COM3 и COM2, COM4 используют одинаковые прерывания. Поэтому, как правило, нельзя одновременно использовать порты COM1 и COM3, так же как порты COM2 и COM4. Например, если к порту COM1 подключен модем, то мышь можно подключить только к порту COM2 или COM4

Теперь перейдем к подробному рассмотрению портов, используемых COM-портами.



Последовательность работы программы


Итак, мы завершили рассмотрение обработчика прерываний. Теперь нам осталось изучить примерный порядок работы коммуникационной программы с использованием прерываний.



Повтор последней введенной команды (команда A/)


A/ - модем повторяет последнюю введенную команду. Команда передается модему без префикса AT и исполняется модемом немедленно, не ожидая прихода символа возврата каретки <CR>. Если вы передадите модему строку AT A/ <CR>, то модем укажет на ошибку и вернет компьютеру слово ERROR.



о модемах. За несколько лет,


В четвертом томе "Библиотеки системного программиста", который называется "Программирование модемов", мы уже рассказывали вам о модемах. За несколько лет, прошедших с момента выхода в свет этого тома, модемы прочно заняли свое место в компьютерах многих фирм и частных владельцев. Появилось много новых моделей модемов и факс-модемов, обеспечивающих большую скорость передачи информации и большую устойчивость к помехам на некачественных телефонных линиях.
Значительно изменилось и программное обеспечение компьютеров. Операционная система Windows и новые программы, разработанные для нее, во многих случаях вытеснили старые программы, предназначенные для работы в операционной системе MS-DOS.
Наша новая книга предназначена не только для тех, кто собирается создавать телекоммуникационные программы, но и для простых пользователей персонального компьютера, использующих уже готовое программное обеспечение. Три первых части книги ориентированы на неподготовленного пользователя, не имеющего даже представления о возможностях применения модемов. Начинающие пользователи узнают о том, что такое модем и факс-модем, как купить и как подключить модем к компьютеру.
Мы расскажем о том, как установить и настроить несколько наиболее распространенных телекоммуникационных программ. Объясним, как передать и принять документ через модем и факс-модем.
В нашей книге мы ориентируемся в первую очередь на пользователей и программистов, работающих в операционной системе Windows. Основной объем информации по телекоммуникационным программам и программированию модемов предназначается именно для них.
По сравнению с предыдущей книгой по модемам мы расширили информацию, предназначенную для программистов, уделили больше внимания программированию современных высокоскоростных модемов.
Последняя, самая большая, глава книги полностью посвящена программированию асинхронных последовательных адаптеров и модемов в среде операционной системы Windows. Эта глава является продолжением нового направления в серии "Библиотеки системного программиста", посвященного операционным системам Windows и Windows for Workgroups.


Используя информацию, представленную в этой книге, и сведения о программировании в Windows, полученные вами из томов серии "Библиотеки системного программиста" или других источников, вы сможете самостоятельно разработать собственные телекоммуникационные программы.
Дополнительно к книге можно купить дискету, содержащую исходные тексты всех разработанных нами программ, описанных в книге. Если вас интересуют сведения, которые были включены в четвертый том "Библиотеки системного программиста", но не вошли в это издание, вы можете отдельно приобрести дискету с гипертекстовой базой данных.
Ниже представлены названия глав книги и их краткое содержание.
Зачем нужны модемы и факс-модемы
В этом разделе мы рассказываем начинающим пользователям о том, для чего нужны и как могут использоваться модемы и факс-модемы. Приведен краткий обзор различных применений модемов и факс-модемов.
Вы узнаете о возможностях использования модемов для обмена документами, о том, что такое глобальные сети и как они могут быть использованы для передачи документов и доступа к удаленным базам данных. Мы расскажем об управлении удаленным компьютером через модем, а также о многих других интересных областях применения модемов.
Модемы и факс-модемы
Советы, приведенные в данной главе, помогут вам при покупке модема или факс-модема. Мы расскажем о том, какие модемы выпускаются современной промышленностью и чем они различаются.
Используя информацию, приведенную в этой главе, вы сможете приобрести модем, имеющий оптимальное соотношение стоимости и реализуемых им возможностей.
Подключение модема к компьютеру
Эта глава призвана оказать помощь при установке модема. Приведенный материал позволит избежать многих подводных камней и поможет сэкономить ваши время и деньги.
Информация, представленная в этой главе может также быть полезна при подключении к компьютеру других устройств: мыши, джойстика, принтеров и т. д.
Телекоммуникационные программы
В настоящее время широкое распространение получили операционные системы Microsoft Windows 3.1 и Microsoft Windows for Workgroups 3.11. Поэтому основное внимание мы уделим телекоммуникационным программам, предназначенным для работы с модемами и факс-модемами в среде Windows.


В первую очередь вы узнаете о телекоммуникационной программе Terminal, встроенной в операционные системы Windows 3.1 и Windows for Workgroups 3.11. Эта программа предоставляет основные средства для работы с модемами, а также позволяет передавать и принимать файлы.
Далее мы изучим приложение Microsoft At Work PC Fax, входящее в состав Windows for Workgroups 3.11 и позволяющее передавать и принимать факсимильные сообщения с помощью факс-модема.
Для тех, кто предпочитает работать в среде операционной системы MS-DOS, мы опишем телекоммуникационную программу Telix и кратко расскажем о возможностях программ MTE, COMIT, BITCOM.
Прочитав эту главу, вы узнаете, как работать с обычными модемами и факс-модемами, сможете самостоятельно передать и принять файл или факс.
Асинхронный адаптер
Рассмотрены основные принципы программирования асинхронных последовательных адаптеров в среде операционной системы MS-DOS. Рассмотрены различные уровни доступа к асинхронному адаптеру, начиная от регистров, функций BIOS, FOSSIL-драйверов и кончая функциями стандартной библиотеки компилятора Borland С++. 
Программирование модемов
Данная глава книги посвящена программированию асинхронных последовательных адаптеров и модемов в среде операционной системы MS-DOS.
В ней вы найдете исходные тексты небольших телекоммуникационных программ, предназначенных для работы в среде операционной системы MS-DOS.
Операционная система Windows
Глава посвящена программированию асинхронных последовательных адаптеров и модемов в среде операционной системы Windows. В ней содержится подробное описание функций программного интерфейса Windows, сообщений и структур данных, предназначенных для взаимодействия с устройствами (модемами, факс-модемами), подключенными к асинхронному последовательному адаптеру.
Материал, изложенный в этой главе, сопровождается исходными текстами простых  телекоммуникационных программ, предназначенных для работы в среде операционной системы Windows.
Приложения
В приложении мы представим вашему вниманию технические параметры интерфейса RS-232-C. Приведем разводку кабелей, используемых при подключении к компьютеру внешнего модема, схему нуль-модема и переходника между широкими и узкими разъемами асинхронного адаптера. Опишем команду MODE, операционной системы MS-DOS.


Как связаться с авторами
Авторы имеют почтовый адрес в сети GlasNet. Все свои замечания и предложения по содержанию книг серий "Библиотека системного программиста" и "Персональный компьютер. Шаг за шагом" вы можете присылать нам по следующему адресу:
frolov@glas.apc.org
Наш почтовый адрес доступен не только пользователям сети GlasNet. Абоненты других компьютерных сетей также могут передать нам сообщения через шлюзы с GlasNet. Ниже мы привели наш адрес в различных сетях:

Глобальная сеть
Наш адрес
CompuServe
>internet:frolov@glas.apc.org
GlasNet
frolov@glas.apc.org
Internet
frolov@glas.apc.org
Relcom
frolov@glas.apc.org
UUCP
uunet!cdp!glas!frolov

Благодарности
Авторы выражают благодарность Фроловой Ольге Викторовне, оказавшей помощь при редактировании рукописи, преподавателю МИФИ Ильинскому Ивану Николаевичу и бессменному корректору серии "Библиотека системного программиста" Кустову Виктору.
Мы также благодарим всех сотрудников издательского отдела АО "ДИАЛОГ-МИФИ": Голубева Олега Александровича, Дмитриеву Наталью, Виноградову Елену, Кузьминову Оксану.

Предметный указатель



А

Алгоритм

Limpel-Ziv, 13

Асинхронный адаптер, 71

Асинхронный порт, 14

Б

Бит четности, 71

Боды, 8

Г

Глобальная сеть, 5

FidoNet, 5

GlasNet, 4, 5, 37

Internet, 5

Relcom, 5

Д

Директива

COMxBase, 186

COMxIRQ, 186

Драйвер

COMM.DRV, 186, 204

FOSSIL, 164

VCD, 191, 194

X00.SYS, 165

И

Идентификатор COM-порта, 187

Интерфейс EasyWin, 220

Использование прерываний, 97

К

Код извещения

CN_EVENT, 213

CN_RECEIVE, 204, 213

CN_TRANSMIT, 204, 213

Команда

%C, 33

&C, 23, 30

&D, 23, 30

&F, 23

&G, 23, 31

&J, 30

&L, 24

&M, 24

&P, 24

&R, 24, 30

&S, 24, 30

&T, 30

&Tn, 24

&V, 26

&W, 27

&Z, 17, 27

\A, 33

\B, 33

\G, 33

\J, 34

\K, 34

\N, 34, 35

класса 1, 35

класса 2, 35

A, 16, 27, 112

A/, 15, 16

AT+FCLASS=?, 35

ATH, 22

ATO, 22

ATS, 27

B, 16

C, 16

D, 16, 28, 30, 47, 111

E, 29, 48

En, 18

F, 18

H, 18, 48, 111

hayes, 15

I, 18

L, 19, 30

M, 19, 31

O, 19

Q, 19, 29, 48

S, 20

V, 29, 48

Vn, 20

X, 21, 28, 31

Y, 22

Z, 22, 47

Команда MS-DOS

DEBUG, 77

MODE, 92, 264

Коммуникационная программа

BITCOM, 41

COMIT, 41

Компрессия данных, 11

Константа

CBR_110, 197

CBR_1200, 197

CBR_128000, 197

CBR_14400, 197

CBR_19200, 197

CBR_2400, 197

CBR_256000, 197

CBR_300, 197

CBR_38400, 197

CBR_4800, 197

CBR_56000, 197

CBR_600, 197

CBR_9600, 197

CE_BREAK, 207

CE_CTSTO, 198, 207

CE_DNS, 207

CE_DSRTO, 198, 207

CE_FRAME, 207

CE_IOE, 207

CE_MODE, 207

CE_OOP, 207

CE_OVERRUN, 207

CE_PTO, 207

CE_RLSDTO, 198, 207

CE_RXOVER, 208

CE_RXPARITY, 199, 208

CE_TXFULL, 208

CSTF_CTSHOLD, 207

CSTF_DSRHOLD, 207

CSTF_EOF, 199, 207

CSTF_RLSDHOLD, 207

CSTF_TXIM, 207

CSTF_XOFFHOLD, 200, 207

CSTF_XOFFSENT, 207

EV_BREAK, 208

EV_CTS, 208

EV_CTSS, 208

EV_DSR, 208

EV_ERR, 199, 208

EV_PERR, 208

EV_RING, 208

EV_RLSD, 208

EV_RLSDS, 208

EV_RXCHAR, 208

EV_RXFLAG, 200, 202, 208

EV_TXEMPTY, 208

EVENPARITY, 197

IE_BADID, 187

IE_BAUDRATE, 187, 197

IE_BYTESIZE, 187, 197



Префикс AT


AT - начало (префикс) командной строки. После получения этой команды модем автоматически подстраивает скорость передачи и формат данных к параметрам компьютера.



Прерывание по принятию данных


Очередной символ принят, и его можно считать через регистр данных. Прерывание сбрасывается после чтения регистра данных. Принятый байт необходимо записать в приемный буфер программы, из которого впоследствии его прочитает основная программа. Буфер приемника удобно организовать в виде очереди.



Прием данных


Аналогично передаче данных перед вводом символа из регистра данных (адрес base_adr) необходимо убедиться в том, что бит 0 регистра состояния линии (адрес base_adr + 5) установлен в 1. Это означает, что символ принят из линии и находится в буферном регистре приемника.

Для приема данных мы используем функцию aux_inp (см. листинг. 5.7).

Листинг 5.7. Файл AUX_INP.C

/**

*.Name         aux_inp

*

*.Descr        Функция дожидается готовности

*              приемника и вводит символ из асинхронного

*              адаптера.

*

*.Proto        char aux_inp(int port);

*

*.Params       int port - номер асинхронного адаптера:

*                 0 - COM1, 1 - COM2

*

*.Return       Принятый символ

*

*.Sample       aux_test.c

**/

#include <stdio.h>

#include <conio.h>

#include "sysp_com.h"

char aux_inp(int port) {

     

unsigned status_reg, inp_reg;

     

status_reg = 0x3fd - 0x100 * port;

     

inp_reg = status_reg - 5;

     

while( (inp(status_reg) & 1) == 0 );

     

return(inp(inp_reg));

}



Прием факсимильных сообщений


Чтобы ваш компьютер мог автоматически принимать факсимильные сообщения, необходимо соответствующим образом настроить Microsoft At Work PC Fax. Для этого в Control Panel необходимо запустить приложение Fax и открыть диалоговую панель "Fax Modem on COM2" (см. рис. 4.19).

Установите переключатель "Answer Mode" в положение "Auto Answer". Затем в поле "Number of Rings" укажите количество звонков, после которого факс-модем должен снять трубку и установить связь.

Вы можете просмотреть полученное факсимильное сообщение при помощи приложения Mail. Пиктограмма этого приложения расположена в группе Network.

Запустите приложение Mail. На экране появится окно "Mail" (см. рис. 4.26). В центральной части окна "Mail" расположены различные пиктограммы. Чтобы просмотреть принятые факсимильные сообщения, следует открыть пиктограмму "Inbox". Для этого щелкните по ней два раза мышью. Внутри окна "Mail" откроется дочернее окно "Inbox", содержащее список принятых факсимильных сообщений. Выделите интересующее вас сообщение и выберите из главного меню приложения строку "View". Откроется новое окно, содержащее нужное сообщение.

Рис. 4.26. Приложение Mail

Если вы отключили режим автоматического ответа на приходящие звонки, тогда чтобы вручную ответить на вызов удаленного факсимильного аппарата сначала необходимо запустить приложение Mail. Затем из меню "Fax" следует выбрать строку "Answer Now!". Факс-модем снимет трубку и начнет прием факсимильного сообщения.

Для просмотра поступившего факсимильного сообщения достаточно выбрать из меню "Fax" строку "View New Fax".



Прием символа с ожиданием


Функция 02h предназначена для чтения очередного символа из буфера приемника драйвера.

На входе:  AH = 02h;

     

DX = номер порта:         0 - COM1, 1 - COM2, 2 - COM3,

                                                              

3 - COM4 и т. д.

На выходе:     AL = принятый байт;

     

AH = 0.

Если буфер приемника пуст, функция ожидает поступления очередного байта из COM-порта.



Приложение EASYTTY


Наше первое телекоммуникационное приложение EASYTTY демонстрирует использование функций Windows, предназначенных для работы с портами асинхронного последовательного адаптера и модемами.

Приложение EASYTTY выполняет все основные функции, которые должна поддерживать любая телекоммуникационная программа. EASYTTY позволяет передавать модему AT-команды, принимать от него ответ и отображать его на экране.

Перед тем как запускать приложение EASYTTY на вашем компьютере, следует создать в каталоге Windows файл EASYTTY.INI (см. листинг 7.6). Если вместе с книгой вы приобрели дискету, то скопируйте файл EASYTTY.INI из каталога WIN\EASYTTY в каталог Windows.

Листинг 7.6. Файл EASYTTY.INI

[Port]

Mode=COM2:9600,N,8,1

Файл EASYTTY.INI должен состоять из одного раздела [Port], содержащего единственную строку Mode. В этой строке определяется номер COM-порта, к которому подключен модем, скорость обмена и формат передаваемых данных. Формат строки Mode совпадает с форматом команды MODE операционной системы MS-DOS.

Запустите EASYTTY. Набирая на клавиатуре AT-команды модема, можно перевести его в любой режим. Например, можно сбросить текущую конфигурацию. Для этого введите команду ATZ и нажмите клавишу <Enter>. В ответ на введенную команду модем загрузит конфигурацию, принятую по умолчанию и вернет компьютеру ответ OK (см. рис. 7.2).

Чтобы приложение EASYTTY могло автоматически отвечать на вызов по телефонной линии, передайте модему команду ATS0=1 и нажмите клавишу <Enter>. Поэкспериментируйте с приложением EASYTTY, передавая модему различные команды и наблюдая на экране ответные сообщения.

Рис. 7.2. Приложение EASYTTY

Вы можете передать модему команду набора номера удаленного абонента. Чтобы набрать номер 777-77-77, достаточно ввести команду ATDP 777-77-77 и нажать клавишу <Enter>.

После установления связи с удаленным модемом вы можете обменяться с ним текстовыми сообщениями. Набирайте передаваемый текст на клавиатуре и просматривайте ответ от удаленного компьютера в главном окне приложения.


Когда вы закончите сеанс связи с удаленным компьютером, переведите модем в командный режим и передайте ему команду ATH0. Модем повесит трубку и отключится от телефонной линии.

Чтобы перевести модем из режима передачи данных в командный режим, подождите 2-3 секунды, наберите на клавиатуре три знака '+' и дождитесь от модема ответа OK.

Главный файл приложения EASYTTY приведен в листинге 7.7.

Листинг 7.7. Файл EASYTTY.CPP

#include    <windows.h>

#include    <string.h>

#include    <conio.h>

#include    <stdio.h>

// Определение констант

#define      QUEUE_SIZE 1024

#define      CTRL_Q          17

#define      CTRL_S          19

#define      ESC                  27

// Прототипы функций

int InitCommPort(void);

int CloseCommPort(int idCommPort);

int ProcessExchange(int idCommPort);

// ============================================================

// Функция WinMain

// ============================================================

#pragma argsused

int PASCAL

WinMain( HANDLE hInstance,

                   HANDLE hPrevInstance,

                   LPSTR lpszCmdLine,

                   int cmdShow )

{

      int  idCommPort;         // идентификатор COM-порта

      // Позволяем одновременно запустить только

      // одну копию приложения

      if( hPrevInstance )

             return( FALSE );

      // Инициализация интерфейса EasyWin

      _InitEasyWin();

      // Открываем COM-порт и устанавливаем новый режим работы

      idCommPort = InitCommPort();

      // В случае возникновения ошибки при инициализации COM-порта

      // завершаем работу приложения

      if(idCommPort < 0)

             return FALSE;

      puts( "Для завершения программы нажмите клавишу <ESC>" );

      while( TRUE )

      {

             MSG    msg;

             // Организуем цикл обработки сообщений

             if( PeekMessage( ( LPMSG )&msg, NULL, 0, 0, PM_REMOVE ) )

             {

                   // При получении сообщения WM_QUIT завершаем приложение



                   if ( msg.message == WM_QUIT )

                         return( msg.wParam );

                   TranslateMessage( &msg );

                   DispatchMessage ( &msg );

             }

             // Если в очереди приложения нет сообщений, начинаем обмен

             // данными через COM-порт

             else

             {

                   if( !ProcessExchange( idCommPort ))

                   {

                         // Закрываем главное окно приложения

                         DestroyWindow(GetFocus());

                   }

             }

      }

}

// ============================================================

// Функция InitCommPort

// ============================================================

int InitCommPort()

{

      DCB    dcbCommPort;            // структура DCB

      int  idCommPort;         // идентификатор COM-порта

      char     szMsg[144];                             // временный буфер

      char     szCommSettings[20];       // режим работы COM-порта

      char     szPortName[6];                        // имя COM-порта

      // Получаем из раздела [Port] файла PHONE.INI строку Mode,

      // определяющую режим работы COM-порта и записываем ее в

      // буфер szCommSettings

      GetPrivateProfileString("Port", "Mode", "COM1:9600,n,8,1",

             szCommSettings, sizeof(szCommSettings) - 1, "easytty.ini");

      // Выделяем из полученной строки первые четыре символа,

      // задающие номер COM-порта, для последующей передачи его

      // функции OpenComm

      lstrcpyn(szPortName, szCommSettings, 5);

      szPortName[4] = '\0';

      // Открываем COM-порт szPortName

      if (( idCommPort =

                         OpenComm( szPortName, QUEUE_SIZE, QUEUE_SIZE )) < 0 )

      {

             // В случае ошибки отображаем сообщение и

             // завершаем работу приложения

             wsprintf( szMsg,

                   "Ошибка открытия порта.\nФункция OpenComm возвратила %d",



                   idCommPort );

             MessageBox( NULL, szMsg,

                                      "Ошибка", MB_OK | MB_ICONEXCLAMATION );

             return( idCommPort );

      }

      // Удаляем все символы из выходной очереди

      FlushComm( idCommPort, 0 );

      // Удаляем все символы из входной очереди

      FlushComm( idCommPort, 1 );

      // Заполняем структуру DCB в соответствии с командной строкой

      // Mode из раздела [Port] файла PHONE.INI

      if( BuildCommDCB( szCommSettings, &dcbCommPort ) != 0 )

      {

             // В случае ошибки отображаем сообщение и

             // завершаем работу приложения

             MessageBox( NULL, "Ошибка при заполнении структуры DCB",

                                      "Ошибка", MB_OK | MB_ICONEXCLAMATION );

             return( -1 );

      }

      // Устанавливаем новый режим COM-порта в соответствии с

      // подготовленной структурой DCB

      if ( SetCommState( &dcbCommPort ) != 0 )

      {

             // В случае ошибки отображаем сообщение и

             // завершаем работу приложения

             MessageBox( NULL, "Ошибка установки режима COM-порта",

                                      "Ошибка", MB_OK | MB_ICONEXCLAMATION );

             return( -1 );

      }

      // Возвращаем идентификатор открытого COM-порта

      return idCommPort;

}

// ============================================================

// Функция ProcessExchange

// ============================================================

int ProcessExchange( int idCommPort )

{

      int nCharWaiting, nCharWriting;

      COMSTAT     ComStat;

      char     inBuff[ QUEUE_SIZE ];

      // Определяем текущее состояние открытого COM-порта

      GetCommError( idCommPort, &ComStat );

      // Если во входной очереди уже есть данные, считываем их и

      // выводим на экран

      if ((nCharWaiting = ComStat.cbInQue ) > 0 )

      {

             // Считываем данные из входной очереди и



             // помещаем их в буффер inBuff

             nCharWaiting = ReadComm( idCommPort, inBuff,

                   ( nCharWaiting > QUEUE_SIZE ?

                   QUEUE_SIZE : nCharWaiting ));

             // Отображаем полученные символы на экране

             if ( nCharWaiting > 0 )

                   for( int i = 0; i < nCharWaiting; i++ )

                         putch(inBuff[i]);

             else

                   return( FALSE );

      }

      // Узнаем, нажата ли какая-нибудь клавиша на клавиатуре

      else if ( kbhit() )

      {

             // Если клавиша нажата, определяем ее код

             char keyHit  = ( char )getch();

             if ( !keyHit )

                   keyHit = ( char )getch();

             // Если нажата клавиша <Esc>, закрываем COM-порт и завершаем

             // работу приложения

             if ( keyHit == ESC )

             {

                   CloseCommPort( idCommPort );

                   return( FALSE );

            }

             // Записывем код нажатой клавиши в выходную очередь

             // COM-порта

             else

             {

                   nCharWriting =

                                WriteComm( idCommPort, ( LPSTR )&keyHit, 1 );

                   // При возникновении ошибки завершаем приложение

                   if( nCharWriting < 0 )

                         return( FALSE );

            }

      }

      return( TRUE );

}

// ============================================================

// Функция CloseCommPort

// ============================================================

int CloseCommPort( int idCommPort )

{

      // Удаляем все символы из входной и выходной очереди

      // COM-порта

      FlushComm( idCommPort, 0 );

      FlushComm( idCommPort, 1 );

      // Закрываем COM-порт

      CloseComm( idCommPort );

      return 0;

}

Особенностью приложения EASYTTY является использование интерфейса EasyWin, предоставляемого средой разработки Borland C++ for Windows версии 3.1. Интерфейс EasyWin позволяет сократить до минимума код, требуемый для создания окна, вывода в него принимаемых данных и получения ввода с клавиатуры.



После запуска приложения EASYTTY, функция WinMain выполняет инициализацию интерфейса EasyWin. Для этого вызывается функция _InitEasyWin, описанная во включаемом файле STDIO.H:

_InitEasyWin();

После вызова этой функции появляется главное окно приложения. Теперь можно вызывать стандартные функции консольного ввода/вывода - puts, kbhit, getch, putch.

Затем функция WinMain вызывает функцию InitCommPort, определенную в приложении. Эта функция считывает из раздела [Port] файла PHONE.INI строку Mode, которая определяет номер COM-порта, к которому подключен модем и его режим работы. Потом InitCommPort открывает соответствующий порт и устанавливает его режим. Затем функция завершает свою работу и возвращает идентификатор открытого COM-порта.

Если COM-порт не открыт, то идентификатор открытого COM-порта равен нулю и приложение сразу завершает работу.

После того как порт открыт, вызывается функция puts:

puts( "Для завершения приложения нажмите клавишу <Esc>" );

Она выводит в окне приложения строку "Для завершения приложения нажмите клавишу <Esc>". Затем следует цикл while в котором вызывается функция PeekMessage и функция ProcessExchange, определенная в нашем приложении:

while( TRUE )

{

      MSG    msg;

      // Организуем цикл обработки сообщений

      if( PeekMessage( ( LPMSG )&msg, NULL, 0, 0, PM_REMOVE ) )

      {

             // При получении сообщения WM_QUIT завершаем приложение

             if ( msg.message == WM_QUIT )

                   return( msg.wParam );

             TranslateMessage( &msg );

             DispatchMessage ( &msg );

      }

      // Если в очереди приложения нет сообщений, начинаем обмен

      // данными через COM-порт

      else

      {

             if( !ProcessExchange( idCommPort ))

             {

                   // Закрываем главное окно приложения

                   DestroyWindow(GetFocus());

             }

      }

}

Функция PeekMessage образует цикл обработки сообщений, благодаря которому одновременно могут работать и другие приложения Windows.



Функция ProcessExchange является сердцем приложения EASYTTY. Она организует весь диалог пользователя с приложением. Для этого она считывает данные из входного буфера COM-порта, поступающие в него от модема, и отображает их в окне приложения. Если вы нажимаете на клавиши, функция ProcessExchange передает код клавиши в выходной буфер COM-порта.

Если вы нажмете клавишу <Esc>, функция ProcessExchange вызывает функцию CloseCommPort, определенную в приложении, а затем закрывает главное окно приложения, вызывая функцию DestroyWindow.

Теперь рассмотрим более подробно функции InitCommPort, ProcessExchange и CloseCommPort, определенные в приложении.

Функция InitCommPort считывает из раздела [Port] файла EASYTTY.INI строку Mode, определяющую режим работы COM-порта и записывает ее в буфер szCommSettings. Если файл EASYTTY.INI не обнаружен в каталоге Windows или в нем не определена строка Mode, в буфер szCommSettings записывается строка "COM1:9600,n,8,1".

Затем из строки в буфере szCommSettings выделяются первые четыре символа, задающие номер COM-порта для последующей передачи его функции OpenComm. Функция OpenComm открывает этот COM-порт.

if((idCommPort =

             OpenComm(szPortName, QUEUE_SIZE, QUEUE_SIZE)) < 0 )

{

...

}

Если COM-порт не открыт, OpenComm возвращает отрицательное значение. Функция отображает предупреждающее сообщение "Ошибка открытия порта..." и завершает работу.

Если COM-порт успешно открыт, удаляем все символы из входной и выходной очередей:

// Удаляем все символы из выходной очереди

FlushComm( idCommPort, 0 );

// Удаляем все символы из входной очереди

FlushComm( idCommPort, 1 );

Затем заполняем структуру dcbCommPort типа DCB в соответствии с командной строкой Mode из раздела [Port] файла EASYTTY.INI. Для этого используем функцию BuildCommDCB, передав ей строку szCommSettings:

if( BuildCommDCB( szCommSettings, &dcbCommPort ) != 0 )

{

      // В случае ошибки отображаем сообщение и

      // завершаем работу приложения



      MessageBox( NULL, "Ошибка при заполнении структуры DCB",

                                "Ошибка", MB_OK | MB_ICONEXCLAMATION );

      return( -1 );

}

Если BuildCommDCB возвращает значение, не равное нулю, значит произошла ошибка. В этом случае выводим сообщение "Ошибка при заполнении структуры DCB" и завершаем функцию, возвращая число -1.

В случае успешного выполнения функции BuildCommDCB устанавливаем новый режим COM-порта в соответствии с подготовленной структурой DCB:

if( SetCommState( &dcbCommPort ) != 0 )

{

      // В случае ошибки отображаем сообщение и

      // завершаем работу приложения

      MessageBox( NULL, "Ошибка установки режима COM-порта",

                                      "Ошибка", MB_OK | MB_ICONEXCLAMATION );

      return( -1 );

}

Если SetCommState возвращает ненулевое значение, значит произошла ошибка. В этом случае выводим сообщение "Ошибка установки режима COM-порта" и завершаем функцию InitCommPort, возвращая число -1.

Если функция SetCommState завершилась успешно, функция InitCommPort завершает работу и возвращает идентификатор открытого COM-порта. Позже полученный идентификатор COM-порта передается функциям ProcessExchange и CloseCommPort.

Функция ProcessExchange вызывает GetCommError, заполняющую структуру ComStat типа COMSTAT:

COMSTAT     ComStat;

// Определяем текущее состояние открытого COM-порта

GetCommError( idCommPort, &ComStat );

Поле cbInQue структуры ComStat будет определять количество символов во входной очереди используемого нами COM-порта. Если во входной очереди есть данные, считываем их и выводим их в окно приложения:

nCharWaiting = ReadComm( idCommPort, inBuff,

                   ( nCharWaiting > QUEUE_SIZE ? QUEUE_SIZE :

                                                                                                                   nCharWaiting ));

// Отображаем полученные символы на экране

if ( nCharWaiting > 0 )

      for( int i = 0; i < nCharWaiting; i++ )      putch(inBuff[i]);



else

      return( FALSE );

Если входная очередь COM-порта пуста, с помощью стандартной функции консольного ввода/вывода kbhit проверяем, нажата ли какая-нибудь клавиша на клавиатуре.

В случае, если клавиша нажата, определяем ее код:

// Если клавиша нажата, определяем ее код

char keyHit  = ( char )getch();

if ( !keyHit )

      keyHit = ( char )getch();

Проверяем, нажата ли клавиша <Esc>. Если нажата клавиша <Esc>, закрываем COM-порт с помощью функции CloseCommPort и возвращаем значение FALSE:

// Закрываем COM-порт и возвращаем значение FALSE

CloseCommPort( idCommPort );

return( FALSE );

Если пользователь нажал любую другую клавишу, записываем ее код в выходную очередь COM-порта:

nCharWriting = WriteComm( idCommPort, ( LPSTR )&keyHit, 1 );

На этом работа функции завершена, и мы переходим к рассмотрению функции CloseCommPort.

Функция CloseCommPort наиболее простая из функций приложения EASYTTY. Она удаляет все символы из входной и выходной очереди COM-порта, а затем закрывает COM-порт и возвращает управление:

FlushComm( idCommPort, 0 );

FlushComm( idCommPort, 1 );

CloseComm( idCommPort );

return 0;

Файл определения модуля для приложения EASYTTY приведен в листинге 7.8.

Листинг 7.8. Файл EASYTTY.DEF

; ==========================================================

; Файл определения модуля

; ==========================================================

NAME EASYTTY

DESCRIPTION 'Приложение EASYTTY, (C) 1994, Frolov G.V.'

EXETYPE windows

STUB 'winstub.exe'

STACKSIZE  16384

HEAPSIZE  16384

CODE preload moveable discardable

DATA preload moveable multiple


Приложение PHONE


Телекоммуникационное приложение EASYTTY демонстрирует основные приемы использования функций Windows, предназначенных для работы с портами асинхронного последовательного адаптера.

Приложение PHONE создано специально для работы с модемами и демонстрирует приемы передачи модему AT-команд и автоматического реагирования на ответ модема. Главная диалоговая панель приложения представляет собой панель телефонного аппарата и показана на рисунке 7.3.

Рис. 7.3. Приложение PHONE

Вы можете набрать на клавиатуре, расположенной в левой части диалоговой панели "Телефон", любой телефонный номер. Введенный номер отображается в специальном поле справа вверху. Если цифра номера введена неправильно, ее можно стереть, нажав на кнопку "<-".

Чтобы модем приступил к набору номера, нажмите кнопку "Набор". Если после набора номера произошло соединение с удаленным модемом, вы можете разорвать связь и повесить трубку, нажав кнопку "Сброс". По окончании работы с приложением, его можно завершить, нажав на кнопку "Выход". Во время инициализации модема, набора номера и попытки установления связи с удаленным модемом завершение приложения не разрешается и кнопка "Выход" блокируется.

Перед тем как запустить приложение PHONE на вашем компьютере, следует создать в каталоге Windows файл PHONE.INI (см. листинг 7.9).

Листинг 7.9. Файл PHONE.INI

[Port]

Mode=COM2:19200,N,8,1

DsrDtrFlow=1

CtsRtsFlow=1

RtsDisable=0

DtrDisable=0

[Modem]

Init=ATQ0V1X4&C1&D2M1

Dial=ATDP

HangUp=ATH0

ModemTimeout=5000

DialTimeout=60000

HangUpTimeout=2000

Файл PHONE.INI должен содержать раздел [Port]. В этом разделе расположены строки, определяющие используемый модемом COM-порт, скорость обмена, формат передаваемых данных, режим управления потоком.

Строка Mode определяет используемый COM-порт, скорость обмена и формат передаваемых данных. Формат строки Mode совпадает с форматом команды MODE операционной системы MS-DOS.

Строки DsrDtrFlow, CtsRtsFlow, RtsDisable и DtrDisable используется для указания режима управления потоком. Числа, указанное вами в этих строках, будут записаны в соответствующие поля структуры DCB.


Далее должен находиться раздел [Modem]. В нем расположены строки, содержащие AT-команды инициализации модема и определены интервалы времени, отведенные на выполнение различных операций.

Строка

Описание

Init

Команда инициализации модема. Инициализация выполняется один раз при запуске приложения PHONE

Dial

Команда набора номера. Передается модему по нажатию кнопки "Набор"

HangUp

Команда разрыва связи. Передается модему по нажатию кнопки "Сброс"

ModemTimeout

Промежуток времени, в течение которого модем должен отреагировать на передачу ему AT-команды

DialTimeout

Промежуток времени, в течение которого модем должен набрать номер и установить связь с удаленным модемом

HangUpTimeout

Величина задержки перед и после передачи модему Escape-последовательности. Используется для переключения модема в командный режим

Временные интервалы, определяемые строками ModemTimeout, DialTimeout и HangUpTimeout, должны быть указаны в миллисекундах.

Исходный текст главного файла приложения PHONE приведен в листинге 7.10.

Листинг 7.10. Файл PHONE.CPP

#define      STRICT

#include    <windows.h>

#include    <string.h>

#include    <stdlib.h>

#include    <mem.h>

#include    <bwcc.h>

#include    "phone.h"

// Глобальные переменные

HINSTANCE  hInst;                     // Идентификатор приложения

int                     idComDev;            // Идентификатор COM-порта

char                        sBufNum[20]; // Буфер для телефонного номера

BOOL                     WaitFlag = FALSE;

// ============================================================

// Функция WinMain

// ============================================================

#pragma argsused

int PASCAL

WinMain( HINSTANCE  hInstance,

                         HINSTANCE  hPrevInstance,

                         LPSTR       lpszCmdLine,

                         int        nCmdShow)

{

      static DLGPROC lpfnDlgProc;



      hInst = hInstance;

      // Переходник для функции диалоговой панели

      lpfnDlgProc =

             (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst);

      // Создаем модальную диалоговую панель

      DialogBox( hInstance, "PHONE", NULL, lpfnDlgProc );

      return 0;

}

// ============================================================

// Функция DldProc

// ============================================================

#pragma argsused

BOOL CALLBACK _export

DlgProc( HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)

{

      int  iErr;

      switch(msg)

      {

             // Инициализация диалоговой панели

             case WM_INITDIALOG:

             {

                   // Посылаем сообщение WM_CONNECT

                   PostMessage( hdlg,WM_CONNECT,0,0L );

                   return TRUE;

             }

             case WM_CONNECT:

             {

                   EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

                   // Открываем COM-порт и инициализируем модем

                   iErr = InitLine();

                   EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);

                   // Если возникла ошибка, отображаем сообщение

                   // и закрываем диалоговую панель

                   if( iErr < 0 )

                   {

                         ShowError( iErr );

                         EndDialog( hdlg, 0 );

                   }

                   return TRUE;

             }

             case WM_COMMAND:

             {

                   switch(wParam)

                   {

                         // Нажата кнопка номеронаберателя

                         case     ID_1: case       ID_2: case       ID_3: case       ID_4: case       ID_5:

                         case     ID_6:   case     ID_7: case       ID_8: case       ID_9: case       ID_0:

                         {

                                int  iNum;

                                char     sTmp[10];

                                // Определяем набранную цифру



                                iNum = wParam - ID_0;

                                wsprintf( sTmp, "%d", iNum );

                                // Добавляем набранную цифру к номеру

                                lstrcat(sBufNum, sTmp);

                                // Обновляем номер на экране

                                SetDlgItemText(hdlg, ID_NUMBER, sBufNum);

                                return TRUE;

                         }

                         // Нажата кнопка "Сброс"

                         case ID_CLEAR:

                         {

                                EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

                                // Вешаем трубку

                                HangUpPhone();

                                EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);

                                return TRUE;

                         }

                         // Удаляем последную цифру номера

                         case ID_BACK:

                         {

                                if( lstrlen( sBufNum ) != 0 )

                                {

                                      sBufNum[lstrlen( sBufNum ) - 1] = '\0';

                                      SetDlgItemText( hdlg, ID_NUMBER, sBufNum );

                                }

                                else MessageBeep( MB_ICONASTERISK );

                                return TRUE;

                         }

                         // Нажата кнопка "Набор"

                         case ID_DIAL:

                         {

                                EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

                                // Набираем номер

                                iErr = DialPhone();

                                // Если возникла ошибка, отображаем сообщение

                                if( iErr < 0 )

                                      ShowError( iErr );

                                EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);



                                return TRUE;

                         }

                         // Нажата кнопка "Выход"

                         case IDCANCEL:

                         {

                                // Закрываем COM-порт

                                CloseLine();

                                // Закрываем диалоговую панель

                                EndDialog( hdlg, 0 );

                                return TRUE;

                         }

                   }

             }

      }

      return FALSE;

}

// ============================================================

// Функция InitLine

// ============================================================

int  InitLine()

{

      BYTE  bSet;

      DCB    dcb;

      int  iErr;

      char     sTmp[200];

      char     szModemAnswer[200];

      char     szInitMode[80];

      char     szTmpStrBool[3];

      char     szInitModem[100];

      long     lnModemTimeout;

      // Определяем режим работы COM-порта. Для этого считываем

      // строку Mode из раздела Port файла PHONE.INI

      GetPrivateProfileString( "Port", "Mode", "COM1:9600,n,8,1",

                   szInitMode, sizeof(szInitMode), "phone.ini" );

      // Открываем COM-порт, заданный в строке szInitMode

      wsprintf( sTmp, "COM%c", szInitMode[3] );

      idComDev = OpenComm( sTmp, 4915, 4915 );

      if (idComDev < 0)

             return ERR_NO_OPEN;

      // Заполняем структуру DCB в соответствии с szInitMode

      iErr = BuildCommDCB( szInitMode, &dcb );

      if(iErr < 0)

             return ERR_DCB_BUILD;

      // Изменяем отдельные поля структуры DCB в

      // соответствии с файлом phone.ini

      // Заполняем поля fOutxDsrFlow, fDtrflow, DsrTimeout

      GetPrivateProfileString( "Port", "DsrDtrFlow", "1",

                                                          szTmpStrBool, sizeof(szTmpStrBool),

                                                          "phone.ini" );



            dcb.fOutxDsrFlow = dcb.fDtrflow = bSet =

                                                         (BYTE) atoi(szTmpStrBool);

      dcb.DsrTimeout = (bSet) ? 3 : 0 ;

      // Заполняем поля fOutxCtsFlow, fRtsflow, CtsTimeout

      GetPrivateProfileString( "Port", "CtsRtsFlow", "1",

              szTmpStrBool, sizeof(szTmpStrBool), "phone.ini" );

      dcb.fOutxCtsFlow = dcb.fRtsflow = bSet =

                                                         (BYTE) atoi(szTmpStrBool);

      dcb.CtsTimeout = (bSet) ? 3 : 0 ;

      // Заполняем поле fRtsDisable

      GetPrivateProfileString( "Port", "RtsDisable", "0",

              szTmpStrBool, sizeof(szTmpStrBool), "phone.ini" );

      dcb.fRtsDisable = (BOOL) atoi(szTmpStrBool);

      // Заполняем поле fDtrDisable

      GetPrivateProfileString( "Port", "DtrDisable", "0",

              szTmpStrBool, sizeof(szTmpStrBool), "phone.ini" );

            dcb.fDtrDisable = (BOOL) atoi(szTmpStrBool);

      // Устанавливаем новый режим работы COM-порта

      iErr = SetCommState( &dcb );

      if( iErr < 0 )

             return ERR_DCB_SET;

      // Удаляем данные из входной и выходной очередей COM-порта

      FlushComm(idComDev, 1);

      FlushComm(idComDev, 0);

      // Подаем сигнал DTR

      EscapeCommFunction(idComDev, SETDTR);

      // Определяем команду инициализации модема, для этого

      // считываем строку Init из раздела Modem файла phone.ini

      GetPrivateProfileString( "Modem", "Init", "ATZ",

              szInitModem, sizeof(szInitModem), "phone.ini" );

      // Добавляем к команде символ перевода строки

      wsprintf( sTmp, "%s\r", szInitModem );

      // Передаем команду инициализации модему

      iErr = WriteComm( idComDev, sTmp, lstrlen( sTmp ) );

      if( iErr < 0 )

             return ERR_WRITE;

      // Определяем время ожидания ответа от модема



      GetPrivateProfileString( "Modem", "ModemTimeout", "1000",

              sTmp, sizeof(sTmp), "phone.ini" );

      lnModemTimeout = atol( sTmp );

      // Ожидаем от модема ответ "OK"

      iErr = WaitModemAnswer(   idComDev, (LPSTR*)szOkString,

                                                   szModemAnswer, 200, (DWORD)lnModemTimeout);

      if( iErr < 0 )

             return iErr;

      return 0;

}

// ============================================================

// Функция DialPhone

// ============================================================

int  DialPhone()

{

      int  iErr;

      char     sTmp[80];

      char     szModemAnswer[200];

      char     szDialModem[80];

      long     lnModemTimeout;

      // Определяем команду набора номера. Для этого считываем

      // строку Dial из раздела Modem файла phone.ini

      GetPrivateProfileString( "Modem", "Dial", "ATDP",

              szDialModem, sizeof(szDialModem), "phone.ini" );

      // Формируем во временном буфере sTmp команду набора номера

      wsprintf( sTmp, "%s%s\r", szDialModem, sBufNum );

      // Удаляем данные из входной и выходной очередей COM-порта

      FlushComm(idComDev, 1);

      FlushComm(idComDev, 0);

      // Передаем модему команду набора номера

      iErr = WriteComm(idComDev, sTmp, lstrlen(sTmp) );

      if( iErr < 0 )

             return ERR_WRITE;

      // Определяем время ожидания ответа от модема

      GetPrivateProfileString( "Modem", "DialTimeout", "30000",

              sTmp, sizeof(sTmp), "phone.ini" );

      lnModemTimeout = atol( sTmp );

      // Ожидаем ответ от модема

      iErr = WaitModemAnswer(   idComDev, (LPSTR*)szAnswer,

                                            szModemAnswer, 200, (DWORD)lnModemTimeout);

      BWCCMessageBox(NULL, szModemAnswer, "Ответ модема", MB_OK );

      return iErr;



}

// ============================================================

// Функция CloseLine

// ============================================================

int  CloseLine()

{

      // Сбрасывааем сигнал DTR

      EscapeCommFunction(idComDev, CLRDTR);

      // Удаляем данные из входной и выходной очередей COM-порта

      FlushComm(idComDev, 1);

      FlushComm(idComDev, 0);

      // Закрываем COM-порт

      CloseComm(idComDev);

      return 0;

}

// ============================================================

// Функция HangUp

// ============================================================

int  HangUpPhone()

{

      DWORD   StartTick;

      char           szHangUpString[80], sTmp[80];

      long           lnModemTimeout;

      // Определяем время задержки перед подачей команды "+++"

      GetPrivateProfileString( "Modem", "HangUpTimeout", "2000",

              sTmp, sizeof(sTmp), "phone.ini" );

      lnModemTimeout = atol( sTmp );

      // Определяем команду разрыва связи. Для этого считываем

      // строку Dial из раздела Modem файла PHONE.INI

      GetPrivateProfileString( "Modem", "HangUp", "ATH0",

              szHangUpString, sizeof(szHangUpString), "phone.ini" );

      // Формируем во временном буфере sTmp команду

      // разрыва соединения

      wsprintf( sTmp, "%s\r", szHangUpString );

      // Определяем текущий момент времени

      StartTick = GetTickCount();

      // Формируем задержку

      while( StartTick + (DWORD)lnModemTimeout > GetTickCount() )

             Idle();

      // Передаем модему последовательность "+++" для перевода его

      // в командный режим

      WriteComm( idComDev, "+++", 3);

      // Определяем текущий момент времени

      StartTick = GetTickCount();

      // Формируем задержку

      while( StartTick + (DWORD)lnModemTimeout > GetTickCount() )

             Idle();

      // Передаем модему команду разорвать соединение и



      // повесить трубку

      WriteComm( idComDev, sTmp, lstrlen( sTmp ) );

      EscapeCommFunction(idComDev, CLRDTR);

      return 0;

}

// ============================================================

// Функция ShowError

// ============================================================

void     ShowError( int iErr )

{

      int  iNum;

      char     szMsgBuff[40];

      // Неизвестная ошибка

      if(( iErr < -6 ) ( iErr >= 0 ))

             return;

      // Загружаем из ресурсов приложения строку

      // с идентификатором iErr

      LoadString( hInst, iErr, szMsgBuff, 40 );

      // Отображаем на экране сообщение об ошибке

      BWCCMessageBox( NULL, szMsgBuff, "Ошибка",

                                       MB_OK | MB_ICONSTOP   );

      // Подаем звуковой сигнал

      MessageBeep( MB_ICONASTERISK );

      return;

}

// ============================================================

// Функция ReadComPort

// ============================================================

int ReadComPort(int idComDev, LPSTR szDest, int nLength)

{

      COMSTAT ComStat;

      int nTotalRead = 0, nRead = 0;

      while(nLength > nTotalRead)

      {

             // Определяем состояние COM-порта

             GetCommError(idComDev,&ComStat);

             // Во входной очереди нет данных

             if (ComStat.cbInQue == 0)

                   break;

             // Считываем данные в буфер szDest

             else

                   nRead = ReadComm(idComDev,&(szDest[nTotalRead]),

                                                                                  nLength - nTotalRead);

             // Если функция ReadComm завершилась с

             // ошибкой, возвращаем -1

             if (nRead < 0)

                   return -1;

             nTotalRead += nRead;

      }

      // Возвращаем количество байт, прочитанных из COM-порта

      return nTotalRead;

}

// ============================================================

// Функция WaitModemAnswer



// ============================================================

int WaitModemAnswer(int idComDev, LPSTR *szWaitDest,

                                        LPSTR szModemAnswer, int nLength,

                                        DWORD dwTimeOut)

{

      int  nRead;

      int  nTotalChar = 0, i;

      DWORD   dwStartTick;

      BOOL  fFind = FALSE;

      // Определяем текущий момент времени

      dwStartTick = GetTickCount();

      do

      {

             // Считываем данные из COM-порта

             nRead = ReadComPort(idComDev, &szModemAnswer[nTotalChar],

                                            nLength - lstrlen(szModemAnswer));

             // В случае ошибки возвращаем ERR_READ

             if(nRead < 0)

                   return ERR_READ;

             else if(nRead > 0)

             {

                   nTotalChar += nRead;

                   // Добавляем в конец полученных данных двоичный ноль

                   szModemAnswer[nTotalChar] = '\0';

                   // Проверяем, получен ли от модема ответ из

                   // массива szWaitDest

                   for(int i = 0; szWaitDest[i]; i++)

                   {

                         if(strstr(szModemAnswer, szWaitDest[i]))

                         {

                                // Если ответ получен, завершаем чтение данных

                                // и возвращаем управвление

                                fFind = TRUE;

                                break;

                         }

                   }

             }

             // Разрешаем обработку сообщений для других приложений

             else

                   Idle();

      // Выходим из цикла, если от модема получен ожидаемый ответ,

      // возникла ошибка при чтении данных, или истекло время

      // ожидания ответа (nTimeOut * 1000)

      } while (!fFind && nRead >= 0 &&

                         dwStartTick + dwTimeOut > GetTickCount() );

      if(nRead >= 0 && !fFind)



             return ERR_TIMEOUT;

      // Возвращаем количесттво пррочитанных символов

      return lstrlen( szModemAnswer );

}

// ============================================================

// Функция Idle

// ============================================================

void     Idle(void)

{

      MSG msg;

      while( PeekMessage( &msg, NULL, NULL, NULL, PM_REMOVE ) )

      {

             TranslateMessage( &msg );

             DispatchMessage( &msg );

      }

      return;

}

Функция WinMain, приложения PHONE, содержит всего несколько строк. В ней нет привычного вызова функций регистрации класса окна и создания окна. Вместо этого WinMain сразу выводит на экран модальную диалоговую панель PHONE, определенную в файле ресурсов приложения PHONE.RC.

Для создания модальной диалоговой панели мы воспользовались функцией DialogBox. Перед вызовом функции DialogBox вызывается функция MakeProcInstance. Она создает переходник для функции диалога DlgProc. Более подробную информацию о создании диалоговых панелей можно получить в 12 томе "Библиотеки системного программиста".

#pragma argsused

int PASCAL

WinMain(HINSTANCE hInstance,

      HINSTANCE hPrevInstance,

      LPSTR     lpszCmdLine,

      int       nCmdShow)

{

      static DLGPROC lpfnDlgProc;

      hInst = hInstance;

      // Переходник для функции диалоговой панели

      lpfnDlgProc =

             (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst);

      // Создаем модальную диалоговую панель

      DialogBox( hInstance, "PHONE", NULL, lpfnDlgProc );

      return 0;

}

Функция диалоговой панели DlgProc обрабатывает сообщения WM_INITDIALOG, WM_CONNECT, WM_COMMAND.

Обработчик сообщения WM_INITDIALOG посылает функции диалоговой панели DlgProc сообщение WM_CONNECT и возвращает значение TRUE:

PostMessage( hdlg,WM_CONNECT,0,0L );

return TRUE;

Сообщение WM_CONNECT определено во включаемом файле PHONE.H следующим образом:

#define WM_CONNECT      WM_USER

Обработчик сообщения WM_CONNECT блокирует кнопку "Сброс" на время инициализации модема, вызывая функцию EnableWindow. После блокировки кнопки "Сброс" вызывается функция InitLine.



// Блокируем кнопку "Сброс"

EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

// Открываем COM-порт и инициализируем модем

iErr = InitLine();

// Разблокируем кнопку "Сброс"

EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);

Функция InitLine, определенная в приложении, открывает COM-порт, устанавливает режим его работы. После этого InitLine передает модему команду инициализации и ждет ответ в течении некоторого времени.

Если модем не возвратил ответ OK, функция InitLine возвращает отрицательную величину. В этом случае приложение сначала вызывает функцию ShowError, которая отображает сообщение об ошибке, а затем функцию EndDialog, которая закрывает диалоговую панель.

// Если возникла ошибка отображаем сообщение

// и закрываем диалоговую панель

if( iErr < 0 )

{

      ShowError( iErr );

      EndDialog( hdlg, 0 );

}

Когда пользователь нажимает на любую кнопку диалоговой панели PHONE, вызывается обработчик сообщения WM_COMMAND. Параметр wParam сообщения WM_COMMAND содержит идентификатор нажатой кнопки. В зависимости от его значения обработчик WM_COMMAND выполняет различные действия.

Если нажата одна из цифровых кнопок "1", "2", "3" ... "0", соответствующая цифра добавляется в конец буфера sBufNum. В этом буфере подготавливается строка с телефонным номером.

Чтобы стереть последнюю цифру номера, можно нажать кнопку "<-". Эта кнопка имеет идентификатор ID_BACK. Обработчик сообщений WM_COMMAND, имеющий параметр wParam, равный ID_BACK, стирает последнюю цифру из буфер sBufNum. Если все символы из буфера sBufNum удалены, вызывается функция MessageBeep, подающая звуковой сигнал.

Набор телефонного номера, записанного в буфере sBufNum, происходит после нажатия на кнопку "Набор". В этом случае в функцию окна приходит сообщение WM_COMMAND с параметром wParam, равным ID_DIAL. Обработчик этого сообщения блокирует кнопку "Выход" и вызывает функцию DialPhone, определенную в приложении, которая и производит набор номера.



Если во время набора номера возникла ошибка, функция DialPhone возвращает отрицательное значение. В этом случае вызывается функция ShowError, которая отображает на экране диалоговую панель с кратким описанием возникшей ошибки. Перед окончанием обработки сообщения разблокируется кнопка "Выход".

case ID_DIAL:

{

      EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

      // Набираем номер

      iErr = DialPhone();

      // Если возникла ошибка, отображаем сообщение

      if( iErr < 0 )

             ShowError( iErr );

      EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);

      return TRUE;

}

Чтобы завершить приложение, надо нажать кнопку "Выход". В этом случае в функцию окна приходит сообщение WM_COMMAND с параметром wParam равным IDCANCEL. Обработчик этого сообщения закрывает COM-порт, вызывая функцию CloseLine.

Затем он вызывает функцию EndDialog, которая закрывает диалоговую панель и завершает приложение.

case IDCANCEL:

{

      // Закрываем COM-порт

      CloseLine();

      // Закрываем диалоговую панель

      EndDialog( hdlg, 0 );

      return 0;

}

После того как модем набрал телефонный номер и соединился с удаленным компьютером, вы можете разорвать это соединение, нажав кнопку "Сброс". При этом вызывается соответствующий обработчик:

case ID_CLEAR:

{

      EnableWindow(GetDlgItem(hdlg, IDCANCEL), FALSE);

      // Вешаем трубку

      HangUpPhone();

      EnableWindow(GetDlgItem(hdlg, IDCANCEL), TRUE);

      return TRUE;

}

Он блокирует кнопку "Выход", а затем вызывает функцию HangUpPhone. Функция HangUpPhone вешает трубку и разрывает связь с удаленным модемом.

Коды ошибок, идентификаторы диалоговой панели PHONE, а также два массива строк szOkString и szAnswer определены в файле PHONE.H (см. листинг 7.11).

Листинг 7.11. Файл PHONE.H

// Прототипы функций

BOOL  CALLBACK _export DlgProc(HWND, UINT, WPARAM, LPARAM);

int  InitLine(void);

int  DialPhone(void);

int  CloseLine(void);

int  HangUpPhone(void);



void     ShowError(int iErr);

void     Idle(void);

int  WaitModemAnswer(int idComDev, LPSTR *szWaitDest,

                                             LPSTR szModemAnswer, int nLength,

                                             DWORD dwTimeOut);

// Определение констант

#define      WM_CONNECT WM_USER

#define      ERR_NO_OPEN         (-1)      

#define      ERR_DCB_BUILD     (-2)      

#define      ERR_DCB_SET          (-3)

#define      ERR_WRITE        (-4)

#define      ERR_TIMEOUT         (-5)

#define      ERR_READ                 (-6)

#define ID_0 100

#define ID_1 101

#define ID_2 102

#define ID_3 103

#define ID_4 104

#define ID_5 105

#define ID_6 106

#define ID_7 107

#define ID_8 108

#define ID_9 109

#define ID_REPEAT 111

#define ID_CLEAR    130

#define ID_BACK     123

#define ID_NUMBER      120

#define ID_DIAL 122

// Определение массивов строк szOkString и szAnswer

char *szOkString[] = {     "OK", NULL };

char *szAnswer[] = {

                                                               "OK",        "CONNECT",

                                                                "RING",    "NO CARRIER",

                                                               "ERROR", "NO DIAL TONE",

                                                               "BUSY",   "NO ANSWER",

                                                               NULL

                                                         };

В листинге 7. 12 представлен исходный текст файла PHONE.RC, содержащего описание ресурсов приложения. В нем описаны диалоговая панель PHONE, пиктограмма PHONE и таблица строк.

Листинг 7.12. Файл PHONE.RC

#include "phone.h"

PHONE DIALOG 59, 29, 131, 79

STYLE WS_POPUP | WS_VISIBLE | WS_BORDER

CLASS "BorDlg"

CAPTION "Телефон"

BEGIN

      PUSHBUTTON "1", ID_1, 8, 9, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP



      PUSHBUTTON "2", ID_2, 24, 9, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "3", ID_3, 40, 9, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "4", ID_4, 8, 25, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "5", ID_5, 24, 25, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "6", ID_6, 40, 25, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "7", ID_7, 8, 41, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "8", ID_8, 24, 41, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "9", ID_9, 40, 41, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "0", ID_0, 24, 57, 30, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "#", ID_REPEAT, 8, 57, 14, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "Выход", IDCANCEL, 78, 57, 32, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "Сброс", ID_CLEAR, 66, 41, 28, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      PUSHBUTTON "Набор", ID_DIAL, 78, 25, 32, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      CONTROL "", ID_NUMBER, "EDIT",   ES_LEFT | ES_READONLY |

             WS_CHILD | WS_VISIBLE | WS_BORDER, 66, 10, 58, 12

      PUSHBUTTON "<--", ID_BACK, 96, 41, 28, 14,

             WS_CHILD | WS_VISIBLE | WS_TABSTOP

      CONTROL "", 110, "BorShade", 1 | WS_CHILD | WS_VISIBLE,

             4, 4, 54, 72

      CONTROL "", 112, "BorShade", 1 | WS_CHILD | WS_VISIBLE,

             62, 4, 66, 72

END

PHONE ICON "phone.ico"

STRINGTABLE

BEGIN

      ERR_NO_OPEN,        "COM-порт не открыт"

      ERR_DCB_BUILD,    "Ошибка DCB"

      ERR_DCB_SET,         " Ошибка при установке режимма COM-порта"



      ERR_WRITE,              "Ошибка при записи в COM-порта"

      ERR_TIMEOUT,        "Модем не отвечает"

      ERR_READ,                "Ошибка чтения из COM-порта"

END

В листинге 7. 13 приведено изображение пиктограммы, расположенной в файле PHONE.ICO, на который ссылается оператор ICON в файле PHONE.RC.

Листинг 7.13. Файл PHONE.ICO



Файл определения модуля для приложения PHONE приведен в листинге 7.14.

Листинг 7.14. Файл PHONE.DEF

; ==========================================================

; Файл определения модуля

; ==========================================================

NAME PHONE

DESCRIPTION 'Приложение PHONE, (C) 1994, Frolov G.V.'

EXETYPE windows

STUB 'winstub.exe'

STACKSIZE   16384

HEAPSIZE            16384

CODE preload moveable discardable

DATA preload moveable multiple


Приложение TELETYPE


Приложение TELETYPE демонстрирует использование телекоммуникационных функций Windows и сообщения WM_COMMNOTIFY для работы с портами асинхронного последовательного адаптера и модемами.

Приложение TELETYPE выполняет все основные функции, которые должна поддерживать любая телекоммуникационная программа. TELETYPE позволяет передавать модему AT-команды, принимать от него ответ и отображать его на экране.

Перед тем как запускать приложение TELETYPE на вашем компьютере, следует создать в каталоге Windows файл TELETYPE.INI (см. листинг 7.15).

Листинг 7.15. Файл TELETYPE.INI

[Port]

Mode=COM2:9600,N,8,1

Файл TELETYPE.INI должен содержать раздел [Port], состоящий из единственной строки Mode. В этой строке определяется номер COM-порта, к которому подключен модем, скорость обмена и формат передаваемых данных. Формат строки Mode совпадает с форматом команды MODE операционной системы MS-DOS.

Запустите TELETYPE. Набирая на клавиатуре AT-команды модема, можно перевести его в любой режим. Можно сбросить текущую конфигурацию. Для этого введите команду ATZ и нажмите клавишу <Enter>. В ответ на введенную команду модем загрузит принятую по умолчанию конфигурацию и вернет компьютеру ответ OK.

Чтобы приложение TELETYPE могло автоматически отвечать на вызов по телефонной линии, передайте модему команду ATS0=1 и нажмите клавишу <Enter>. Поэкспериментируйте с приложением TELETYPE, передавая модему различные команды и наблюдая на экране ответные сообщения.

Рис. 7.4. Приложение TELETYPE

Вы можете передать модему команду набора номера удаленного абонента. Чтобы набрать номер 987-65-43 достаточно ввести команду ATDP 987-65-43 и нажать клавишу <Enter>.

После установления связи с удаленным модемом вы можете обменяться с ним текстовыми сообщениями. Набирайте передаваемый текст на клавиатуре и просматривайте ответ от удаленного компьютера в главном окне приложения.

Когда вы закончите сеанс связи с удаленным компьютером, переведите модем в командный режим и передайте ему команду ATH0. Модем повесит трубку и отключится от телефонной линии.


Чтобы перевести модем из режима передачи данных в командный режим, подождите 2-3 секунды, наберите на клавиатуре три знака '+' и дождитесь от модема ответа OK. Внешний вид главного окна приложения TELETYPE представлен на рисунке 7.4.

Главный файл приложения TELETYPE приведен в листинге 7.16. После запуска TELETYPE управление получает главная функция приложения WinMain.

Если параметр hPrevInstance функции WinMain равен нулю, WinMain вызывает функцию инициализации приложения InitApp. В противном случае на экран выводится сообщение о невозможности запуска второй копии приложения.

Функция InitApp регистрирует класс окна WMODEM и возвращает управление функции WinMain. На базе зарегистрированного класса окна создается и отображается главное окно приложения, после чего запускается обычный цикл обработки сообщений.

При создании окна в функцию окна передается сообщение WM_CREATE. Получив это сообщение, функция окна вызывает функцию InitTTY, которая определяет различные параметры окна и сохраняет их в глобальных переменных. Затем вызывается функция PostMessage, которая отправляет сообщение WM_CONNECT функции главного окна приложения. Сообщение WM_CONNECT определено во включаемом файле TELETYPE.H следующим образом:

#define WM_CONNECT      WM_USER

Константа WM_USER специально предназначена для определения приложениями своих собственных кодов сообщений.

Обработчик сообщения WM_CONNECT вызывает функцию InitCommPort. Эта функция открывает и инициализирует COM-порт в соответствии с данными из файла TELETYPE.INI, разрешает генерацию драйвером COM-порта сообщений WM_COMMNOTIFY и устанавливает сигнала DTR.

После обработки сообщения WM_CONNECT можно набирать на клавиатуре текст. Для получения ввода с клавиатуры наше приложение обрабатывает сообщение WM_CHAR, вызывая функцию UserChat, которая передает код введенного символа драйверу COM-порта и отображает его на экране.

Меню приложения TELETYPE содержит две строки: "Информация" и "Выход". При выборе строки "Информация" на экране отображается короткая информация о приложении.



Когда вы закончите работать с TELETYPE, завершите приложение, выбрав из меню строку "Выход". В этом случае вызывается функция PostMessage, передающая функции окна сообщение WM_CLOSE.

Обработчик сообщения WM_CLOSE вызывает функцию CloseCommPort, которая закрывает COM-порт и завершает приложение.

Листинг 7.16. Файл TELETYPE.CPP

// Определяем константу MAIN_MODULE. Она используется

// в файле TELE.H

#define MAIN_MODULE

#include <windows.h>

#include <mem.h>

#include <bwcc.h>

#include "tele.h"

// Имя класса главного окна приложения

char     szClassName[] = "WMODEM";

// Заголовок главного окна приложения

char     szWindowTitle[] = "Телетайп";

//=============================================================

// Функция WinMain

//=============================================================

#pragma argsused

int PASCAL

WinMain( HINSTANCE hInstance,

                   HINSTANCE  hPrevInstance,

                   LPSTR       lpszCmdLine,

                   int        nCmdShow)

{

      MSG  msg;   // структура для работы с сообщениями

      HWND hwnd;  // идентификатор главного окна приложения

      // Проверяем, не запускалось ли это приложение ранее

      if(!hPrevInstance)

      {

             // Сохраняем в глобальной переменной hInst идентификатор

             // приложения

             hInst = hInstance;

             // Если не запускалось, вызываем функцию InitApp

             if(!InitApp(hInstance))

                   return FALSE;

      }

      else

      {

             MessageBeep(MB_ICONASTERISK);

             BWCCMessageBox( NULL,

                   "Можно запускать только одну копию приложения",

                   "Ошибка", MB_OK | MB_ICONSTOP);

             return FALSE;

      }

      // Создаем главное окно приложения

      hwnd = CreateWindow(

             szClassName,

             szWindowTitle,

             WS_OVERLAPPED | WS_VISIBLE,

             CW_USEDEFAULT, CW_USEDEFAULT,



             CW_USEDEFAULT, CW_USEDEFAULT,

             NULL, NULL,

             hInstance,

             NULL);   

      // Если создать окно не удалось, завершаем приложение

      if(!hwnd)

      {

             MessageBeep(MB_ICONASTERISK);

             BWCCMessageBox( NULL,

                   "Ошибка при создании главного окна приложения",

                   "Ошибка", MB_OK | MB_ICONSTOP);

             return FALSE;

      }

      // Рисуем окно

      ShowWindow(hwnd, nCmdShow);

      // Передаем функции окна сообщение WM_PAINT

      UpdateWindow(hwnd);

      // Запускаем цикл обработки сообщений

      while (GetMessage(&msg,NULL,0,0))

      {

             TranslateMessage(&msg);

             DispatchMessage(&msg);

      }

      return msg.wParam;

}

// ============================================================

// Функция InitApp

// ============================================================

BOOL

InitApp(HINSTANCE hInstance)

{

      ATOM aWndClass;        // атом для кода возврата

      WNDCLASS wndclass;  // структура для регистрации

                                                               // класса окна

      // Запмсываем нулевые значения во все поля структуры

      memset(&wndclass, 0, sizeof(wndclass));

      wndclass.style                        = CS_HREDRAW | CS_VREDRAW;

      wndclass.lpfnWndProc         = (WNDPROC) WndProc;

      wndclass.cbClsExtra        = 0;

      wndclass.cbWndExtra           = 0;

      wndclass.hInstance         = hInstance;

      wndclass.hIcon                       = LoadIcon(hInstance, "PHONE");

      wndclass.hCursor                   = LoadCursor( NULL, IDC_ARROW );

      wndclass.hbrBackground     = GetStockObject( WHITE_BRUSH );

      wndclass.lpszMenuName     = (LPSTR) "APP_MENU";

      wndclass.lpszClassName      = (LPSTR) szClassName;

      // Регистрируем класс окна szClassName

      aWndClass = RegisterClass(&wndclass);

      // Возвращаем результат регистрации класса



      return(aWndClass != 0);

}

// ============================================================

// Функция окна WndProc

// ============================================================

LRESULT CALLBACK _export

WndProc( HWND hwnd,

                         UINT message,

                         WPARAM wParam,

                         LPARAM lParam )

{

      switch( message )

      {

             case WM_CREATE:

             {

                   // Определяем параметры окна

                   InitTTY( hwnd );

                   // Отправляем сообщение WM_CONNECT

                   PostMessage( hwnd,WM_CONNECT,0,0L );

                   return 0;

             }

             case WM_CONNECT:

             {

                   // Открываем и инициализируем COM-порт

                   idOpenCommPort = InitCommPort(hwnd);

                   // В случае ошибки отображаем сообщение и завершаем

                   // приложение

                   if (idOpenCommPort < 0)

                   {

                         MessageBeep(MB_ICONASTERISK);

                         BWCCMessageBox(hwnd,"COM-порт не открыт","Ошибка",

                                MB_ICONSTOP | MB_OK);

                         PostMessage(hwnd,WM_CLOSE,0,0L);

                   }

                   return 0;

             }

             case WM_COMMNOTIFY:

             {

                   // Вызываем обработчик сообщения WM_COMMNOTIFY

                   ProcessCommNotify( hwnd, wParam, LOWORD( lParam ));

                   return 0;

             }

             case WM_SETFOCUS:

             {

                   // Приложение получило фокус ввода

                   SetFocusTTY(hwnd);

                   return 0;

             }

             case WM_KILLFOCUS:

             {

                   // Приложение потеряло фокус ввода

                   KillFocusTTY(hwnd);

                   return 0;

             }

             case WM_CHAR:

             {

                   // Пользователь нажал на клавишу



                   UserChat(hwnd, message, wParam, lParam);

                   return 0;

             }

             case WM_COMMAND:

             {

                   switch ( wParam )

                   {

                         case CM_EXIT:

                         {

                                PostMessage( hwnd, WM_CLOSE, 0, 0L );

                                break;

                         }

                         case CM_ABOUT:

                         {

                                About(hwnd);

                                break;

                         }

                   }

                   return 0;

             }

             case WM_CLOSE:

             {

                   // Закрываем COM-порт и завершаем приложение

                   CloseCommPort(idOpenCommPort);

                   DestroyWindow( hwnd );

                   PostQuitMessage( 0 );

                   return 0;

             }

             default:

                   return( DefWindowProc( hwnd, message, wParam, lParam ) );

      }

}

// ============================================================

// Функция About

// ============================================================

void About(HWND hwnd)

{

      BWCCMessageBox(hwnd,

                   "Телекоммуникационная программа\n\n"

                   "(C) Фролов Г.В., 1994",

                   "Информация",

                   MB_OK | MB_ICONINFORMATION);

      return;

}

В файле CONNECT.CPP (см. листинг 7.17) содержится определение функции UserChat. Эта функция предназначен для обработки сообщения WM_CHAR, поступающего функции окна в ответ на ввод с клавиатуры.

Функция UserChat проверяет, нажал ли пользователь клавишу <Enter> (<Return>). Если нажал, то в рабочий массив szTemp записываются три символа '\r', '\n' и '\0'. Таким образом, в szTemp записывается строка "\r\n". В противном случае в szTemp записывается только код нажатой клавиши, содержащийся в параметре wParam и символ '\0'.



Строка, подготовленная в массиве szTemp, передается в COM-порт и далее модему.

WriteComm( idOpenCommPort,(LPSTR)szTemp,

                                 lstrlen((LPCSTR)szTemp));

После вызова функции WriteComm приложение вызывает функцию GetCommError, которая сбрасывает код ошибки, если таковая случилась при передаче. В нашем приложении полученный код ошибки не обрабатывается.

Переданная модему строка не отображается автоматически в окне приложения. Для этого строка, записанная в szTemp, передается функции WriteTTY, определенной в файле TTY.CPP.

      WriteTTY( szTemp, hwnd, DARK_BLUE );

Первый параметр функции WriteTTY должен содержать строку, которую надо отобразить на экране, второй параметр - идентификатор окна и третий - цвет отображаемой строки. Мы указали в качестве третьего параметра константу DARK_BLUE, соответствующую синему цвету. Константы DARK_BLUE и DARK_GREEN, используемые в нашем приложении определены в файле TELETYPE.H.

Листинг 7.17. Файл CONNECT.CPP

#include <windows.h>

#include "tele.h"

// ============================================================

// Функция UserChat

// ============================================================

#pragma argsused

LRESULT

UserChat(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

      char     szTemp[3];      // Рабочий массив

      switch (wParam)

      {

             // Пользователь нажал клавишу <Return> (<Enter>)

             case VK_RETURN:

                   szTemp[0] = '\r';

                   szTemp[1] = '\n';

                   szTemp[2] = '\0';

                   break;

             // Пользователь нажал другую клавишу (не <Return>)

             default:

                   szTemp[0] = (char)wParam;

                   szTemp[1] = '\0';

                   break;

      }

      // Передаем код нажатой клавиши в COM-порт

      WriteComm( idOpenCommPort,(LPSTR)szTemp,

                                      lstrlen((LPCSTR) szTemp));

      // Получаем и сбрасываем код ошибки



      GetCommError( idOpenCommPort,NULL );

      // Отображаем символ нажатой клавиши на экране

      WriteTTY( szTemp, hwnd, DARK_BLUE );

      return 0;

}

В файле COMMPORT.CPP (см. листинг 7.18) определены основные функции, непосредственно взаимодействующие с COM-портом - InitComPort, CloseComPort и ReadCommPort.

Функция InitComPort выполняет все действия по инициализации COM-порта. Рассмотрим ее более подробно.

Сначала InitComPort определяет режим работы COM-порта, для этого она считывает строку Mode из раздела Port файла TELETYPE.INI. Затем функция OpenComm открывает соответствующий COM-порт.

Потом функция BuildCommDCB заполняет структуру DCB, которая передается функции SetCommState. Функция SetCommState устанавливает новый режим работы порта. Сразу после открытия COM-порта в его буферах могут остаться данные. Чтобы их удалить, мы использовали функцию FlushComm.

В приложении TELETYPE мы обрабатываем сообщения WM_COMMNOTIFY, вырабатываемые драйвером COM-порта. Для того чтобы разрешить генерацию этих сообщений, предназначена функция EnableCommNotification.

if(!EnableCommNotification(idCommPort,hwnd,32,-1))

      return -1;

После вызова EnableCommNotification функции окна будут поступать сообщения WM_COMMNOTIFY с кодом извещения CN_RECEIVE, если во входную очередь COM-порта поступило больше 32 символов или истек тайм-аут. Более подробную информацию о функции EnableCommNotification можно получить в разделах "Функция EnableCommNotification" и "Сообщение WM_COMMNOTIFY".

Затем вызывается функция EscapeCommFunction, которая устанавливает сигнал DTR, сообщая модему, что компьютер готов к обмену данными.

На этом функция InitCommPort заканчивает свою работу и возвращает вызывающей процедуре идентификатор открытого COM-порта.

Самая простая функция нашего приложения, предназначенная для  работы с COM-портами, называется CloseComPort. Она сбрасывает сигнал DTR и закрывает COM-порт.

В файле COMMPORT.CPP также определена функция ReadComPort, предназначенная для чтения данных из выходной очереди COM-порта.



Функция имеет три параметра. Первый параметр idComDev определяет идентификатор COM-порта, из которого будут прочитаны данные. Второй параметр szDest должен содержать адрес буфера, в который будет записана поступающая информация, а последний параметр nLength указывает размер этого буфера.

Функция ReadCommPort содержит внутри себя цикл, в котором происходит чтение данных из выходной очереди COM-порта. Мы выполняем чтение из входной очереди в цикле, так как при больших скоростях передачи информации (больше 9600 бит/с) за время чтения данных из очереди, в нее могут поступить новые данные.

В цикле сначала вызывается функция GetCommError. Она заполняет структуру ComStat типа COMSTAT. Нас интересует только поле cbInQue этой структуры. В нем записано, сколько байт находится во входной очереди COM-порта. Если в очереди есть данные, считываем их, вызывая функцию ReadComm. В противном случае выходим из цикла чтения и возвращаем вызывающей процедуре количество прочитанных байт.

Листинг 7.18. Файл COMMPORT.CPP

#include <windows.h>

#include "tele.h"

// ============================================================

// Функция InitComPort

// ============================================================

int InitCommPort(HWND hwnd)

{

      DCB    dcb;                                           // структура DCB

      int  idCommPort;               // идентификатор COM-порта

      char     szPort[6];                     // имя порта

      char     szInitMode[40];   // режим работы

      int  nResult;                             // временная переменная

      // Определяем режим работы COM-порта, для этого считываем

      // строку Mode из раздела Port файла phone.ini

      GetPrivateProfileString("Port", "Mode", "COM2:2400,n,8,1",

                                      szInitMode, sizeof(szInitMode), "teletype.ini");

      // Открываем COM-порт, заданный в строке szInitMode

      wsprintf( szPort, "COM%c", szInitMode[3] );



      idCommPort = OpenComm(szPort, INQUEUE, OUTQUEUE);

      if (idCommPort < 0)

             return idCommPort;

      // Заполняем структуру DCB в соответствии с szInitMode

      nResult = BuildCommDCB(szInitMode,&dcb);

      if (nResult < 0)

             return nResult;

      // Устанавливаем новый режим работы COM-порта

      nResult = SetCommState(&dcb);

      if (nResult < 0)

             return nResult;

      // Удаляем данные из входной и выходной очередей COM-порта

      FlushComm(idCommPort, 1);

      FlushComm(idCommPort, 0);

      // Разрешаем генерацию сообщения WM_COMMNOTIFY

      if(!EnableCommNotification(idCommPort,hwnd,32,-1))

             return -1;

      // Подаем сигнал DTR

      EscapeCommFunction(idCommPort, SETDTR);

      return idCommPort;

}

// ============================================================

// Функция CloseComPort

// ============================================================

int CloseCommPort(int idCommPort)

{

      // Сбрасываем сигнал DTR

      EscapeCommFunction(idCommPort, CLRDTR);

      // Закрываем COM-порт

      CloseComm(idCommPort);

      return 0;

}

// ============================================================

// Функция ReadComPort

// ============================================================

int ReadCommPort( int idComDev, LPSTR szDest, int nLength)

{

      COMSTAT ComStat;

      int nTotalRead = 0, nRead = 0;

      // Цикл чтения данных из входной очереди COM-порта

      while(nLength > nTotalRead)

      {

             // Определяем, есть ли данные во входной очереди

             GetCommError(idComDev,&ComStat);

             if (ComStat.cbInQue == 0)

                   break;

             // Считываем данные из входной очереди COM-порта

             nRead = ReadComm(idComDev,&(szDest[nTotalRead]),

                                                                                  nLength - nTotalRead);

             // Возникла ошибка

             if(nRead < 0)



             {

                   nTotalRead = -nTotalRead - nRead;

                   break;

             }

             nTotalRead += nRead;

      }

      return nTotalRead;

}

Файл COMMSG.CPP (см. листинг 7.19) содержит определение только одной функции ProcessCommNotify. Эта функция выполняет обработку сообщений WM_COMMNOTIFY, вырабатываемых COM-портом.

При получении сообщения WM_COMMNOTIFY с кодом извещения CN_RECEIVE функция ProcessCommNotify считывает данные из входной очереди COM-порта, вызывая функцию ReadCommPort.

Если ReadCommPort возвратила положительную величину, отображаем на экране прочитанную информацию при помощи функции WriteTTY. Обратите внимание, что данные полученные из COM-порта, отображаются зеленым цветом, в то время как данные передаваемые в COM-порт - синим.

Если функция ReadCommPort возвратила отрицательную величину, значит при чтении из COM-порта возникла ошибка, например, был получен байт с ошибкой по четности. В этом случае мы также отображаем полученные данные на экране, а затем вызываем функцию GetCommError, которая определяет код ошибки и сбрасывает регистр ошибок.

Листинг 7.19. Файл COMMSG.CPP

#include <windows.h>

#include "tele.h"

// ============================================================

// Функция ProcessCommNotify

// ============================================================

int ProcessCommNotify(HWND hwnd, int nComID, int nNotification)

{

      int  nResult;                                          // временная переменная

      char     szData[OUTQUEUE+1];        // временный буфер данных

      // Получено сообщение WM_COMMNOTIFY с кодом извещения

      // CN_RECEIVE

      if ( nNotification & CN_RECEIVE )

      {

             // Считываем данные из входной очереди COM-порта

             nResult = ReadCommPort ( nComID, (LPSTR)szData, OUTQUEUE );

             if (nResult > 0)

             {

                   szData[nResult] = 0;

                   // Отображаем считанные из COM-порта данные в окне



                   WriteTTY(szData, hwnd, DARK_GREEN);

             }

             // Возникла ошибка при чтении из COM-порта

             else if (nResult < 0)

             {

                   szData[-nResult] = 0;

                   // Отображаем считанные из COM-порта данные в окне

                   // и возвращаем код ошибки

                   WriteTTY(szData, hwnd, DARK_GREEN);

                   return nResult;

             }

             // Определяем код ошибки и сбрасываем регистр ошибок

            GetCommError(nComID,NULL);

      }

      return nResult;

}

Предусмотрены четыре функции, предназначенные для работы с главным окном приложения TELETYPE. Их имена - InitTTY, WriteTTY, SetFocusTTY, KillFocusTTY. Эти функции определены в файле TTY.CPP (см. листинг 7.20). Так как указанные функции не содержат ничего, непосредственно относящегося к COM-портам, мы опишем их кратко.

Функция InitTTY определяет размеры окна приложения в символах. Главное окно приложения не может изменять свой размер. Поэтому функцию InitTTY достаточно вызвать в начале работы приложения. Прототип функции представлен ниже:

void InitTTY(HWND hwnd);

Функция InitTTY имеет только один параметр hwnd, который должен содержать идентификатор главного окна приложения. Это значение возвращается функцией CreateWindow.

Функция WriteTTY предназначена для отображения данных в главном окне приложения. Прототип функции:

void WriteTTY(LPSTR lpOutString, HWND hwnd,

                                            COLORREF rgbColor);

Первый параметр lpOutString должен содержать указатель на строку символов, закрытую двоичным нулем. Эта строка будет выведена на экран.

Второй параметр hwnd должен содержать идентификатор главного окна приложения.

Последний параметр функции WriteTTY - rgbColor. Он определяет цвет символов, которые будут отображаться на экране. В приложении TELETYPE символы, полученные от модема, отображаются зеленым цветом, а символы набираемые пользователем на клавиатуре и передаваемые модему - синим.



Функции SetFocusTTY и KillFocusTTY управляют текстовым курсором, отображаемым в окне приложения. Функция SetFocusTTY отображает курсор, а функция KillFocusTTY - убирает его из окна приложения. Прототипы функций SetFocusTTY и KillFocusTTY аналогичны:

void SetFocusTTY(HWND hwnd);

void KillFocusTTY(HWND hwnd);

Эти функции имеют единственный параметр hwnd, который должен содержать идентификатор главного окна приложения.

Листинг 7.20. Файл TTY.CPP

#include <windows.h>

#include "tele.h"

// Текущее положение курсора

static int    nyCurrRow, nxCurrCol;

// Размер главного окна приложения в символах

static int    nyRows, nxCols;

// Размер символов

static int    nxCharSize, nyCharSize;

// ============================================================

// Функция InitTTY

// ============================================================

void InitTTY(HWND hwnd)

{

      HDC                              hdc;                                    // индекс контекста устройства

      TEXTMETRIC            tiTextMetric;  // структура для записи метрик

                                                                                         // шрифта

      RECT                            rcTTYWindow;          // размер окна

      // Определяем начальное положение курсора

      nyCurrRow = 0;   

      nxCurrCol = 0;

      // Получаем контекст отображения

      hdc = GetDC( hwnd );

      // Выбираем в контекст отображения шрифт OEM_FIXED_FONT

      SelectObject(hdc, GetStockObject(OEM_FIXED_FONT));

      // Заполняем структуру tiTextMetric информацией о метрике

      // шрифта, выбранного в контекст отображения

      GetTextMetrics(hdc, &tiTextMetric);

      // Освобождаем контекст

      ReleaseDC( hwnd, hdc );

      // Запоминаем среднее значение ширины символов

      nxCharSize = tiTextMetric.tmAveCharWidth;

      // Запоминаем значение высоты символов с учетом

      // межстрочного интервала

      nyCharSize = tiTextMetric.tmHeight +

                                 tiTextMetric.tmExternalLeading;



      // Определяем текущее размеры окна

      GetClientRect(hwnd, &rcTTYWindow);

      // Определяем количество строк, помещающихся в окне

      nyRows = (rcTTYWindow.bottom - rcTTYWindow.top) / nyCharSize;

      // Определяем количество символов, помещающихся в строке окне

      nxCols = (rcTTYWindow.right - rcTTYWindow.left) / nxCharSize;

      return;

}

// ============================================================

// Функция WriteTTY

// ============================================================

void

WriteTTY( LPSTR lpOutString, HWND hwnd, COLORREF rgbColor )

{

      LPSTR       lpCurrChar;     // рабочий указатель 

      HDC          hdc;                              // индекс контекста устройства

      // Получаем контекст отображения

      hdc = GetDC( hwnd );

      // Выбираем в контекст отображения шрифт OEM_FIXED_FONT

      SelectObject( hdc, GetStockObject(OEM_FIXED_FONT));

      // Устанавливаем цвет текста

      SetTextColor(hdc, rgbColor);

      // Устанавливаем цвет фона текста соответствующий цвету окна

      SetBkColor(hdc, GetSysColor(COLOR_WINDOW));

      // Устанавливаем режим вывода текста

      SetBkMode(hdc, OPAQUE);

      // Выключаем курсор

      HideCaret( hwnd );

      // Отображаем строку lpOutString в окне

      for ( lpCurrChar = lpOutString; *lpCurrChar; lpCurrChar++ )

      {

             switch ( *lpCurrChar )

             {

                   // Возвращаем курсор на одну позицию назад

                   case ASCII_BACK:

                   {

                         if(nxCurrCol)

                                nxCurrCol--;

                         break;

                   }

                   // Переводим курсор в начало текущей строки

                   case ASCII_CR:

                   {

                         nxCurrCol = 0;

                         break;

                   }

                   // Переводим курсор на новую строку

                   case ASCII_LF:

                   {

                         nyCurrRow++;



                         // При необходимости выполняем вертикальную

                         // свертку окна

                         if ( nyCurrRow == ( nyRows - 1 ) )

                         {

                                ValidateRect( hwnd, NULL );

                                ScrollWindow( hwnd, 0, -nyCharSize, NULL, NULL );

                                // Передаем сообщение WM_PAINT

                                UpdateWindow( hwnd );

                                // Изменяем текущее положение курсора

                                nyCurrRow = nyRows - 2;

                         }

                         break;

                   }

                   // Подаем звуковой сигнал

                   case ASCII_BELL:

                   {

                         MessageBeep(MB_OK);

                         break;

                   }

                   default:

                   {

                         // Отображаем очередной символ из строки

                         // lpOutString в текущей позиции окна

                         TextOut( hdc, nxCurrCol * nxCharSize,

                                                         nyCurrRow * nyCharSize, lpCurrChar, 1 );

                         // Смещаем курсор вправо

                         nxCurrCol++;

                         // При необходимости переходим на следующую строку

                         if ( nxCurrCol == ( nxCols - 1 ) )

                         {

                                nxCurrCol = 0;

                                nyCurrRow++;

                                // Если это необходимо, выполняем вертикальную

                                // свертку экрана

                                if ( nyCurrRow == ( nyRows - 1 ) )

                                {

                                      ValidateRect( hwnd, NULL );

                                      ScrollWindow( hwnd, 0, -nyCharSize, NULL, NULL );

                                      UpdateWindow( hwnd );

                                      nyCurrRow = nyRows - 2;



                                }

                         }

                         break;

                   }

             }

      }

      // Перемещаем курсор в новую позиицию

      SetCaretPos( nxCurrCol * nxCharSize,

                                                   nyCurrRow * nyCharSize );

      // Отображаем курсор

      ShowCaret( hwnd );

      // Освобождаем контекст

      ReleaseDC( hwnd, hdc );

      return;

}

// ============================================================

// Функция SetFocusTTY

// ============================================================

void SetFocusTTY(HWND hwnd)

{

      // Создаем текстовый курсор

      CreateCaret( hwnd, NULL, nxCharSize, nyCharSize );

      // Перемещаем курсор в текущую позиицию

      SetCaretPos( nxCurrCol * nxCharSize,

             nyCurrRow * nyCharSize );

      // Отображаем курсор

      ShowCaret( hwnd );

      return;

}

// ============================================================

// Функция KillFocusTTY

// ============================================================

void KillFocusTTY(HWND hwnd)

{

      // Выключаем курсор

      HideCaret(hwnd);

      // Удаляем курсор

      DestroyCaret();

      return;

}

Включаемый файл TELE.H (см. листинг 7.21) содержит определения констант, идентификаторов и глобальных переменных, а также объявления функций, используемых в приложении.

Листинг 7.21. Файл TELE.H

#define DARK_BLUE            RGB(0,0,127)   // синий цвет

#define DARK_GREEN   RGB(0,127,0)   // зеленый цвет

// Идентификаторы меню приложения

#define CM_EXIT     101

#define CM_ABOUT      102

// Сообщение WM_CONNECT

#define WM_CONNECT      WM_USER

// Размеры входной и выходной очереди

#define INQUEUE      4096

#define OUTQUEUE 4096

// ASCII-коды

#define ASCII_BELL 0x07

#define ASCII_BACK     0x08    

#define ASCII_LF            0x0A

#define ASCII_CR           0x0D

// Определяем глобальные переменные только в главном модуле

#ifdef MAIN_MODULE

      HINSTANCE hInst;



      int idOpenCommPort;

#else

// Объявляем глобальные переменные,

// определенные в главном модуле

      extern int idOpenCommPort;

      extern HINSTANCE hInst;

#endif

// Функции, определенные в файле TELETYPE.CPP

void           About(HWND hwnd);

BOOL              InitApp(HINSTANCE hInstance);

LRESULT CALLBACK _export

WndProc ( HWND hWnd, UINT message, WPARAM wParam,

                                LPARAM lParam);

// Функция, определенная в файле COMMSG.CPP

LRESULT

UserChat(HWND hWnd, UINT message,

                          WPARAM wParam, LPARAM lParam);

// Функция, определенная в файле COMMSG.CPP

int

ProcessCommNotify(HWND hWnd, int nComID, int nNotification);

// Функции, определенные в файле COMMPORT.CPP

int InitCommPort( HWND hwnd);

int CloseCommPort( int nComID );

int ReadCommPort( int nComID , LPSTR Data , int nMaxLength );

// Функции, определенные в файле TTY.CPP

void InitTTY(HWND hWnd);

void WriteTTY(LPSTR lpOutString, HWND hwnd, COLORREF rgbColor);

void SetFocusTTY(HWND hwnd);

void KillFocusTTY(HWND hwnd);

В листинге 7.22 представлен исходный текст файла TELETYPE.RC, содержащего описание ресурсов приложения TELETYPE. В нем описаны меню APP_MENU и пиктограмма PHONE.

Листинг 7.22. Файл TELETYPE.RC

#include "tele.h"

PHONE     ICON "teletype.ico"

APP_MENU MENU

BEGIN

      MENUITEM "Выход",                                    CM_EXIT

      MENUITEM "Информация",                CM_ABOUT

END

В листинге 7.23 приведено изображение пиктограммы, расположенной в файле TELETYPE.ICO, на который ссылается оператор ICON в файле описания ресурсов TELETYPE.RC.

Листинг 7.23. Файл TELETYPE.ICO



Файл определения модуля для приложения TELETYPE приведен в листинге 7.24.

Листинг 7.24. Файл TELETYPE.DEF

; ==========================================================

; Файл определения модуля

; ==========================================================

NAME TELETYPE

DESCRIPTION 'Приложение TELETYPE, (C) 1994, Frolov G.V.'

EXETYPE windows

STUB 'winstub.exe'

STACKSIZE   16384

HEAPSIZE            16384

CODE preload moveable discardable

DATA preload moveable multiple


Приложение Terminal


Операционные системы Windows 3.1, Windows for Workgroups 3.11 и Windows NT имеют в своем составе простую телекоммуникационную программу Terminal. С помощью этой программы можно полностью управлять модемом и организовать обмен файлами с удаленным компьютером.

К сожалению, Terminal обладает минимумом возможностей, что заметно сужает возможную область его применения.

Пиктограмма Terminal расположена в группе Accessories приложения Program Manager.

Запустите приложение Terminal. На экране появится главное окно "Terminal - (Untitled)" (см. рис. 4.1).

Рис. 4.1.   Главное окно приложения Terminal

Внешний вид главного окна приложения Terminal напоминает приложение Notepad. Ниже заголовка расположена строка меню.

Перед тем как начать работу с модемом, необходимо соответствующим образом настроить характеристики приложения Terminal - указать порт асинхронного адаптера, к которому подключен модем, определить скорость передачи информации модему и формат, в котором передаются данные. Затем следует определить AT-команды, которые будут применяться для управления модемом: инициализации модема, набора номера и прекращения сеанса связи. Для настройки всех этих параметров предназначено меню "Settings".

Конфигурацию, выбранную в меню "Settings", можно сохранить в файле с расширением TRM. Впоследствии вы сможете загрузить такой файл. При этом автоматически устанавливается сохраненная в файле конфигурация. Для сохранения и восстановления конфигурации предназначено меню "File".

Чтобы установить связь с удаленным модемом и передать ему файл, необходимо, чтобы модем набрал номер абонента. Операцию набора номера и разрыва связи можно выполнить из меню "Phone".

Когда номер набран и связь между модемами установлена, вы можете набирать в главном окне приложения текст, который отображается на экране и передается удаленному модему. Одновременно в окне приложения Terminal выводятся символы, принятые вашим модемом от удаленного компьютера.

Если вам надо передать или принять от удаленного компьютера файл, следует воспользоваться меню "Transfers". Несмотря на то, что приложение Terminal поддерживает только два простых протокола передачи файлов - Kermit и XModem CRC, для передачи не очень больших файлов этого вполне достаточно.

Меню "Edit" позволяет копировать информацию из главного окна приложения в Clipboard - универсальный обменный буфер Windows и обратно.

И, наконец, меню "Help", имеющее одинаковое назначение во всех приложениях Windows, предназначено для получения подсказки или инструкции по работе с приложением.



Приложения


В приложении мы приведем описание расширенного набора команд и регистров Hayes-модемов, представим формат регистров асинхронного адаптера.

Для тех, кто не боится взять в руки паяльник, мы приведем разводку кабелей, используемых при подключении к компьютеру внешнего модема, схему нуль-модема и переходника между широкими (DB25) и узкими (DB9) разъемами асинхронного адаптера.



Принципы программирования модемов


Доступ к модему происходит через последовательный асинхронный порт. При этом для передачи модему команд их необходимо просто записать в регистр данных COM-порта, на котором находится модем. Ответ от модема также поступает через последовательный порт. Передавая модему команды, его можно проинициализировать, перевести в режим автоответа или заставить набрать номер.

Когда модем наберет номер удаленного абонента или когда модему в режиме автоответа придет вызов, он попытается установить связь с удаленным модемом. После установления связи модем передает компьютеру через COM-порт специальное сообщение (см. главу "Система команд hayes-модемов") и переключится из командного режима в режим передачи данных. После этого данные, передаваемые модему, перестают восприниматься им как команды и сразу передаются по телефонной линии на удаленный модем.

Итак, после установления связи с удаленным модемом, коммуникационная программа может начинать обмен данными. Обмен данными так же, как и передача команд, осуществляется через COM-порт. Затем при помощи специальной Escape-последовательности можно переключить модем из режима передачи данных обратно в командный режим и положить трубку (AT-команда ATH0), разорвав связь с удаленным модемом.

Принципы обмена данными с внешними устройствами через COM-порт представлены в главе "Асинхронный адаптер".

Приведем основную последовательность действий для установления связи и обмена данными через модем без использования прерываний от асинхронного адаптера.



Программа Bitcom


Bitcom, версии 3.584 - коммуникационная программа, часто поставляемая в комплекте с модемами, не имеющими аппаратной реализации MNP. Выполняет программную эмуляцию протоколов MNP (до MNP5 включительно).

Bitcom поддерживает следующие протоколы обмена файлами: Xmodem, Xmodem CRC, Ymodem, Ymodem-G, Kermit, CompuServe Plus, ASCII. Отсутствует наиболее удобный протокол Zmodem.

Следует также отметить, что в Bitcom неудачно реализован интерфейс с пользователем.



Программа CHATINT


В этой главе мы приведем исходный текст коммуникационной программы 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


Программа Comit


Comit, версии 1.27b - удобная коммуникационная программа, часто поставляется в комплекте с модемами, не имеющими аппаратной реализации MNP. Выполняет программную эмуляцию протоколов MNP2, MNP4, MNP5, MNP7.

К сожалению, эта коммуникационная программа имеет очень бедный набор протоколов для обмена файлами. Поддерживаются протоколы Xmodem, Xmodem CRC, Ymodem, Ymodem-G и ASCII.



Программа для определения типа микросхемы UART


Теперь приведем программу TST_UART, реализующую изложенный алгоритм. В данной программе используются созданные нами функции is_UART_8250() и is_UART_FIFO(). Первая позволяет определить по отсутствию регистра расширения микросхему UART 8250, а вторая по особенностям реализации внутреннего буфера данных различает остальные типы микросхем.

Исходный текст программы TST_UART представлен в листинге 5.1.

Листинг 5.1. Файл TST_UART.C

// Программа определения типа микросхемы UART асинхронного

// последовательного адаптера

#define    UART_8250     1

#define    UART_16450    2

#define    UART_16550    3

#define    UART_16550A   4

void main(void) {

     

// Номер асинхронного порта может быть 0 для COM1

     

// или 1 для COM2

     

int port = 0;

     

int test;

     

printf("\n(c) Frolov G.V. 1992-1994.   "

               

"Программа определения типа UART\n\n");

     

printf(  "\Введите номер асинхронного"

                               

"порта (COM1 - 0, COM2 - 1):");

     

scanf( "%d", &port );

     

if(( port != 0 ) && ( port != 1 )){

            

printf( "асинхронный порт COM%d не поддерживается\n",

                                     

port );

            

exit( -1 );

     

}

     

// Проверяем, является ли микросхема UART - UART 8250

     

if( is_UART_8250(port) == UART_8250 ) {

            

printf("Обнаружена микросхема UART 8250\n");

            

exit(0);

     

}

     

// Проверяем другие типы микросхем UART

     

if(( test = is_UART_FIFO(port) ) == UART_16550A ) {

            

printf("Обнаружена микросхема UART 16550A\n");

            

exit(0);

     

}

     

else if(test == UART_16550) {

            

printf("Обнаружена микросхема UART 16550\n");

            

exit(0);

     

}

     

printf("Обнаружена микросхема UART 16450\n");

}

/**

*.Name         is_UART_8250

*

*.Descr        Функция определяет тип микросхемы,

*              используемый данным последовательным асинхронным


*              адаптером (UART).

*

*.Proto        int is_UART_8250( int port );

*

*.Params       int port - номер асинхронного адаптера:

*                 0 - COM1, 1 - COM2

*

*.Return       Для UART 8250 - возвращает константу UART_8250,

*              в остальных случаях возвращает 0

**/

int is_UART_8250( int port ) {

      int save_scr, in_scr;

      // Сохраняем значения регистра расширения

      save_scr = inp( 0x3ff - 0x100 * port );

      // Записываем в регистр расширения число 0x5A

      outp( 0x3ff - 0x100 * port, 0x5A );

      // Считываем регистр расширения

      in_scr = inp( 0x3ff - 0x100 * port  );

      // Сохранилось ли записанное число?

      if( in_scr != 0x5A ) {

             // Если нет, значит, регистр расширения отсутствует и,

             // следовательно, тип микросхемы - UART 8250

             // Восстанавливаем значение регистра расширения

             outp( 0x3ff - 0x100 * port, save_scr );

             return( UART_8250 );

      }

      // Записываем в регистр расширения другое число - 0xA5

      outp( 0x3ff - 0x100 * port, 0xA5 );

      // Считываем регистр расширения

      in_scr = inp( 0x3ff - 0x100 * port  );

      // Восстанавливаем значение регистра расширения

      outp( 0x3ff - 0x100 * port, save_scr );

      // Сохранилось ли записанное число?

      if( in_scr != 0xA5 )

             // Если нет, регистр расширения отсутствует и,

             // следовательно, тип микросхемы - UART 8250

             return( UART_8250 );

      // В противном случае регистр расширения есть и надо

      // выполнить дальнейшее тестирование для определения

      // типа UART

      return( 0 );

}

/**

*.Name         is_UART_FIFO

*

*.Descr        Функция определяет тип микросхемы,

*              используемой данным последовательным асинхронным

*              адаптером (UART).

*

*.Proto        int is_UART_FIFO( int port );

*

*.Params       int port - номер асинхронного адаптера:

*                 0 - COM1, 1 - COM2

*



*.Return       для UART 164550 возвращает константу UART_16450,

*              для UART 16550  возвращает константу UART_16550,

*              для UART 16550A возвращает константу UART_16550A

**/

int is_UART_FIFO( int port ) {

      int save_iir, in_iir;

      // Сохраняем значение регистра определения прерывания

      save_iir = inp( 0x3fa - 0x100 * port );

      // Разрешаем использование FIFO

      outp( 0x3fa - 0x100 * port, 0x1 );

      // Читаем значение регистра определения прерывания

      in_iir = inp( 0x3fa - 0x100 * port  );

      // Восстанавливаем  значение регистра определения прерывания

      outp( 0x3fa - 0x100 * port, 0x0 );

      // Если бит D6 содержит единицу, значит, мы имеем UART 16550A

      if(( in_iir & 0x40 ) == 1 )

             return( UART_16550A );

      // Если бит D7 содержит единицу, значит, мы имеем UART 16550

      if(( in_iir & 0x80 ) == 1 )

             return( UART_16550 );

      // Если биты D7 и D6 содержат нули, значит, мы имеем UART

      // 16450 (буфер FIFO отсутствует)

      return( UART_16450 );

}


Программа MTE


Широко распространенная коммуникационная программа фирмы MagicSoft, Inc. В MTE версии 2.10g программно реализован протокол MNP. Поэтому, если ваш модем не имеет аппаратной реализации протокола MNP, использование MTE позволяет установить устойчивую связь с MNP-модемами (или с не MNP-модемами, работающими под управлением MTE).

MTE имеет следующие встроенные протоколы обмена файлами: ASCII, Xmodem, Xmodem CRC, Ymodem, Ymodem-G, Zmodem, Kermit.



Программа Telix


Telix, версия 3.25 - удобная коммуникационная программа, поддерживающая много протоколов обмена файлами. В Telix реализованы следующие протоколы: Kermit, Modem7, SEAlink, Telink, Xmodem, Xmodem-1k, Ymodem, Ymodem-G, Zmodem, ASCII.

Однако Telix имеет один маленький недостаток: он не поддерживает программной эмуляции MNP. Вследствие этого его невозможно использовать на сильно зашумленных линиях с модемами, не обеспечивающими аппаратную коррекцию ошибок.

Запустите программу Telix. На экране компьютера появится сообщение о запуске программы. Нажмите клавишу <Enter>. Telix выполнит инициализацию асинхронного последовательного адаптера и модема. В нижней части экрана появится текстовая строка с информацией о состоянии программы, COM-порта и модема. Вы сразу можете набирать на клавиатуре компьютера AT-команды. Они будут переданы модему, а его ответ появится на экране.

Телекоммуникационная программа Telix поддерживает много различных возможностей, которые вызываются при нажатии специальных комбинаций клавиш. Получить полный список таких комбинаций и их краткое описание можно нажав комбинацию клавиш <Alt+Z>.



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


Данная глава книги будет посвящена программированию асинхронных последовательных адаптеров и модемов в среде операционной системы MS-DOS. Мы опишем основные принципы работы телекоммуникационных программ с использованием и без использования прерываний от асинхронного адаптера.

В этой главе вы найдете исходные тексты небольших телекоммуникационных программ, предназначенных для работы в среде операционной системы MS-DOS.



Произошло прерывание по линии состояния


Считать регистр состояния линии и уточнить причину прерывания (данное прерывание сбрасывается после чтения регистра состояния линии). Если это необходимо, подать основной программе сигнал о произошедшей ошибке с целью ее устранения. Например, в случае определения на линии сигнала BREAK (удаленный модем повесил трубку), надо попытаться возобновить связь.