Книга
Книга
Введение
В книге рассматриваются вопросы программирования, которые по непонятным причинам довольно скудно освещены в доступной литературе, — программирование последовательных коммуникационных устройств. В Интернете и на книжных полках можно найти множество книг, посвященных аппаратной организации последовательной связи. А вот вопросы программирования почему-то ограничиваются описанием битов порта или заголовками функций для работы с портами. Хочется надеяться, что представленная книга заполнит этот пробел.
Почему выбран именно последовательный интерфейс? Простота и низкие аппаратные требования (в сравнении, например, с параллельным интерфейсом) делают его заманчивым методом реализации систем обмена информацией. Видимо по этой причине форумы по программированию заполнены вопросами "Как работать с COM-портом из Windows?”,"Как прочитать данные с порта" и т. д. И хотя USB-интерфейс постепенно вытесняет СОМ, последний остается пока наиболее распространенным, например, в системах промышленной автоматизации.
Операционная система Windows имеет настолько много функций, что начинающий программист просто не знает с чего начать и где искать информацию. Тем более, что функции для работы с портами вовсе не называются OpenComPort, а конфигурирование параметров порта ничем не напоминает программирование в DOS. Конечно, MSDN Library (Microsoft Development Network Library) содержит ответы на все эти вопросы, но, во-первых, только на английском языке, и, во-вторых, только на уровне заголовков функций, описания их параметров и скелетов программ. Наша книга содержит и описания, и примеры использования как низкоуровневых функций DOS, так и функций Windows.
Книга построена таким образом, чтобы переход из DOS в Windows и из Windows 98 в Windows 2000/ХР стал достаточно простым и минимально трудоемким.
Все исходные коды приведены на прилагаемом компакт-диске.
Для кого эта книга
Мы адресуем книгу нескольким категориям читателей:
口 тем, кому необходимо быстро разобраться в принципах работы с последовательными портами и интерфейсами, не углубляясь в дебри теории;
тем, кого не устраивает стандартный уровень, предоставляемый функциями DOS или Windows, и кому требуется более тонкое программирование порта;
тем, кому досталась поддержка чужих программ, работающих с оборудованием;
тем, кому необходимо быстро переделать программу, работающую в старых операционных системах (например, DOS, Windows 95/98) в Windows 2000/ХР, а времени на полную переработку нет:
П тем, кто хочет повысить свой профессиональный уровень;
П инженерам, связанным с электроникой, желающим написать программу для работы с оборудованием, не вникая в тонкости программирования;
О ну, и наконец, просто любителям подключать к компьютеру разные устройства и проводить эксперименты.
Даже если вы не относите себя ни к одной из вышеперечисленных категорий, не стоит сразу отставлять книгу на полку. Например, занимаясь не поел едовател ьными, а параллельными интерфейсами, вы можете найти необходимую информацию о создании драйверов и INF-файлов для них. Для тех, кого интересуют вопросы программирования "железа”, полезным станет описание прямого доступа к портам в Windows 2000/ХР и т. п.
Вы не найдете в этой книге ни подробных описаний аппаратной части по- следовател ьн ых интерфейсов, ни рекомендаций по разворач и ван и ю сети на основе RS-485, ни формул для расчета максимальной длины проводника... Зато вы сможете найти листинги и исходные тексты действительно работающих программ с русскими комментариями.
Структура книги
Книга состоит из трех частей. В первой части даются теоретические сведения о п осл ед о вате л ьн ы х ннтер力 е 访 сах, протоколах, методах коррекции ошибок и т. д. Вторая часть книги ——практическая, про нее мы расскажем далее. В третьей части книги приводятся справочные материалы, описание функций, портов, регистров и другие необходимые для работы сведения.
Практически вся вторая часть построена на основе одного примера: передачи данных с одного последовательного порта на другой. В качестве приемника и передатчика можно использовать два компьютера, но это затруднительно как в плане их наличия, так и при отладке программы (если конечно между компьютерами нет локальной сети). В нашей книге мы будем ориентироваться на один компьютер и передавать данные с одного последовательного порта на другой. Все примеры построены по принципу усложнения задачи: от совсем простых примеров в DOS мы дойдем до сложных асинхронных программ в Windows.
Сначала мы будем работать в DOS. Для этого существует несколько причин. Во-первых, эта информация может потребоваться программистам, которым досталась поддержка чужих программ. Примеры будут построены таким образом, что переход в Windows будет достаточно простым делом. Более того、 специальные модули позволят обращаться к коммуникационным портам из Windows NT/2000/XP точно так же, как это делается в DOS. Во-вторых, достаточно часто DOS (или его клон) используется в качестве операционной системы промышленных компьютеров. Ну, и наконец, не зная основ, читателю будет сложно ориентироваться в сложных функциях Windows. В DOS мы пройдем полный путь от простейшей программы в несколько строк, использующей сервис BIOS, до программы, обрабатывающей прерывания коммуникационного порта.
Программирование в Windows мы начнем с создания программы, в точности повторяющей структуру программы в DOS. Мн даже заставим работать такую программу в Windows 2000/ХР. Затем освоим функции Windows. На этом пути мы познакомимся с синхронной и асинхронной связью, научимся использовать потоки и функции синхронизации. Отдельно рассмотрим функции, которые были добавлены только в Windows 2000/ХР.
Программирование в Windows мы завершим созданием программы, демонстрирующей работу Plug and Play. Мы создадим эмулятор устройства, работающий с портом С0М1, 11 заставим Windows найти новое устройство на порту COM2.
В отдельной главе мы рассмотрим интерфейс RS-485, требующий более глубоких знаний о работе коммуникационного порта.
Одна из глав будет посвящена вычислению контрольных сумм и реализации протоколов обмена.
В приложениях вы сможете найти ответы на наиболее часто задаваемые вопросы по программированию коммуникационных портов и некоторые полезные функции.
Краткое описание глав
Главы 1—5 содержат теоретическую часть книги.
口 Глава I ("Стандарты последовательной с вязии) дает общие представления об аппаратной части последовательных протоколов, разъемах и о самом стандарте. Специального заострения на аппаратной части не делается. Мы расскажем о многих RS-протоколах, принципах последовательной связи, кратко опишем разъемы и кабели, а также дадим представление о ресурсах и функциях компьютера.
口 В главе 2 (“Протоколн”)расематриваются основные методы организации последовательной связи с точки зрения программных протоколов обмена.
Мы расскажем о текстовых и бинарных протоколах, о методах предотвращения и обнаружения потери данных.
口 В главе 3 (иПоеледовательные протоколы IBM PC") мы расскажем об использовании последовательной связи в персональном компьютере. Мы не будем рассматривать аппаратные реализации интерфейсов, а только лишь протоколы обмена устройств с компьютером. Эта глава будет полезна не только тем, кто интересуется работой устройств компьютера, но и тем, кто думает о создании своих протоколов обмена.
口 В главе 4 (''Промышленные последовательные протоколы11) рассмотрена возможность использования последовательных протоколов обмена в промышленных условиях. Мы познакомимся с протоколами MODBUS, ProfiBus и CAN. Опять же, мы будем рассматривать только программную составляющую протоколов, оставляя аппаратную реализацию за рамками книги.
О В главе 5 (мСОМ-порты и Plug and Play”)мы расскажем о работе системы автоматического обнаружения устройств, дадим некоторые сведения о формате INF-файла, необходимого для установки драйвера устройства, и полностью опишем алгоритм работы Plug and Play для поеледовательных портов.
Главы 6—17 содержат практическую часть книги.
О Глава 6 ("Использование сервисов BIOS”)начинает практическую часть с самой простой программы последовательной связи. Использование сервисов BIOS позволяет написать программу последовательного обмена всего из нескольких строк. К сожалению, такая программа будет очень ограничена: сервис BIOS не позволяет использовать микросхему UART на полную мощность. Эта глава будет интересна тем, кому приходится применять чужие модули последовательной связи, использующие сервисы BIOS. Кроме того, промышленные компьютеры с клоном DOS обычно предоставля- ют именно такой способ работы с внешними устройствами.
В главе 7 С'Прямое программирование портов*') избавиться от ограничений BIOS нам поможет прямое обращение к портам. Эти сведения будут полезны не только программистам, работающим в DOS, но и тем, кто создает свой драйвер для Windows.
Использование прямого доступа не избавляет нас от неприятной необходимости постоянного опроса порта. В главе 8 ("Использование обработки прерываний1') мы научимся обрабатывать прерывания последовательного порта и на этом завершим программирование в режиме DOS. Сведения этой главы пригодятся программистам, использующим промышленные контроллеры с установленным DOS.
Глава 9 (''Переход в Windows*1) демонстрирует переход из DOS в Windows. Приведенные здесь программы еще не используют функций Windows, но
уже работают под управлением операционных систем Microsoft. Эта глава будет полезна тем, кто хочет перенести свои программы из DOS в Windows с минимальными изменениями. Вы сможете работать с портами в Windows 2000/ХР так же как в DOS.
口 В главе 10 ("Использование функций Windows”) мы, наконец, доберемся до непосредственного использования функций Windows. Так же как в DOS мы начнем с простейшей программы обмена, но уже используя функции Windows.
□ Глава 11 ("Использование потоков**) по смыслу похожая на главу 8 (мИспользование обработки прерываний**), рассказывает о том, как разгрузить основной процесс от постоянного опроса порта в Windows.
口 Отдельный поток, созданный в главе 11, к сожалению, не избавляет от необходимости постоянного опроса порта, а значит, лишней загрузки процессора. В главе 12 (Ифункции асинхронного доступа и события**) используются асинхронные функции работы с портом совместно с функциями событий, которые позволяют нам создать поток, ^просыпающийся*' только в момент появления новых данных. Как мы покажем в нашем примере, загрузка процессора при этом будет практически незаметна.
口 В главе 13 (^Специальные коммуникационные функции11) мы опишем функции, которые не требовались в предыдущих главах, но потребуются в следующих. Кроме того, будут описаны функции, которые появились только в Windows 2000.
□ До сих пор мы занимались приемом и передачей только одного байта. Для реальной программы этого маловато. В главе 14 (нРеализация протоколов обмена**) мы создадим специальные компоненты, которые позволят нам разделить транспортные функции последовательного порта и функции работы с конкретным протоколом обмена.
□ В главах теоретической части мы описывали протоколы обмена, такие как MODBUS, протоколы модемов XModem, YModem и другие, а в главе 15 (^Вычисление контрольных сумм") приведем примеры вычисления контрольных сумм, применяемых в этих протоколах.
□ Несмотря на договоренность не рассматривать аппаратных реализаций протоколов, интерфейс RS-485 заслуживает отдельной главы 16 (°Интерфейс RS-485"), т. к. его программная реализация требует дополнительных знаний и функций.
□ Конечно, описание протокола Plug and Play, которое мы привели в главе 5, достаточно интересно. Но ведь всегда гораздо интереснее попробовать самому! Программа из главы 17 (мРабота Plug and Play') будет эмулировать присутствие некоторого устройства, подключенного к порту COM2 с помощью обработки сообщений порта СОМ1. Также мы напишем INF-файл, устанавливающий 'драйверы** нашего устройства.
Главы 18—23 содержат справочную часть книги.
Глава 18 ("Сервис BIOS INT14H/INT21H'*) — справочник функций BIOS.
Глава 19 (иПорты IBM РСМ) — справочник коммуникационных портов и их значений. С этими сведениями мы пользовались в главах 7——9.
口 Глава 20 (''Структуры Windows для работы с СОМ-портамии) — справочник структур Windows и описания полей этих структур.
Глава 21 СФункции Windows для работы с СОМ-портамн")—— справочник функций Windows.
Глава 22 ("Функции DeviceioControl для последовательного порта")—— описание кодов прямого доступа к драйверу последовательного порта.
Глава 23 ("АТ-команды и регистры модемов'*)——краткий справочник основных АТ-команд и регистров модемов.
Программные требования
Все программы мы будем реализовывать на языках Borland Pascal 7.0 и Borland Delphi 6. Поскольку мы не будем использовать никаких специфических функций, присущих именно этим версиям языков, все примеры могут быть скомпилированы в других версиях практически без модификации.
Версия Windows может быть любой — от Windows 98 до Windows 2000/ХР. Конечно, примеры работы с функциями, которые появились только в Windows 2000, не будут работать в Windows 98, но обратной совместимости мы будем придерживаться: даже прямое программирование портов станет доступным в Windows 2000 с помощью специального модуля.
Все примеры, приведенные в книге, тестировались в версиях Windows 98 SE, Windows 2000 Workstation и Windows 2000 Advanced Server.
Для компиляции драйверов, приведенных в книге, потребуется MS Visual Studio и Windows 2000 DDK. На компакт-диске содержатся уже готовые модули драйверов, так что эту часть можно пропустить.
О программном коде
Книга содержит полные исходные коды всех программ, однако многие листинги содержат только изменения кода относительно предыдущего листинга. Такое сокращение позволяет не только экономить место, но и улучшить понимание кода, делая акцент только на новой функциональности. Код на компакт-диске содержит полные тексты без сокращений.
В программах для DOS мы не будем приводить код функций работы со строками. Код этих функций можно найти в приложениях или на компакт-диске к книге. В программах на Delphi мы не приводим код самого
проекта (DPR-файл) и код формы (ОРКД-файлы). Все исходные коды можно найти на компакт-диске. Еше раз повторим, что такие со крашения не означают отсутствие возможности скомпилировать ■ попробовать приведенные примеры на своем компьютере, а преследуют цель экономии места. Все необходимые исходные коды и модул и каходятся на компакт-диске.
Обозначения
При написании чисел мы будем придержи виться следующих правил:
шестнадцатеричные числа будут иметь префикс 飞”,например ”$45";
шестнадцатеричные числа могут иметь префикс “Ох" или постфикс ”Н”, если того требует контекст изложения или формат строки, например "NT 14Н”;
битовые последовательиости заключены в угловые скобки, например У0010>”.
Аппаратные требования
Достаточно обычного домашнего компьютера, на котором компиляция программы в Delphi занимает приемлемое для вас время.
Для тестирования последовательно力 связи необходимо иметь либо два компьютера, либо один компьютер с двумя п ос л едовател ьн ы м и портами. В последнем случае нам потребуется PS/2- или USB-мышь, т. к. оба последовательных порта мы займем для экспериментов.
Для соединения портов между собой нам потребуется нуль-модемный кабель, желательно полностью распаянный, но для большей части программ достаточно трехпроводного кабеля.
Благодарности
В первую очередь автор хочет поблагодарить заместителя главного редактора Евгения Рыбакова. Без его советов и критики эта книга никогда бы не была создана. Автор благодарит всех друзей и родных, которые терпели его в процессе написания книги, а также коллектив издательства *'БХВ- Петербургм. Отдельная благодарность Вадиму Шарикову (компания Bixel, http://bixel.ru), дизайнеру сайта PvaSoft.
Обратная связь
Обо всех ошибках или пожеланиях сообщайте по адресу books©pvasoft.com.
ЧАСТЬ I
Протоколыи ИНТЕРФЕЙСЫ
Глава 1
Стандарты последовательной связи
Глава дает общие представления об аппаратной части последовательных протоколов, разъемах и о самом стандарте. Специального заострения внимания на аппаратной части не делается.
Стандарты последовательной связи
Последовательный интерфейс используется для связи двух устройств между собой. Данные в одну сторону передаются по одному проводу с помощью поеледовательности битов.
Мы будем рассматривать подключение одного или нескольких устройств к компьютеру, т. к. такая комбинация наиболее часто используется в реальной жизни. Естественно, при подключении нескольких устройств к компьютеру обмен производится только с одним из этих устройств.
Для соединения со стороны компьютера используется интерфейс, называемый COM-порт (COMmunication port, коммуникационный порт). Этот порт обеспечивает асинхронный обмен и реализуется на микросхемах универсальных асинхронных приемопередатчиков (UATR, Universal Asynchronous Receiver Transmitter), совместимых с семейством i8250. Хотя стандарт RS-232c предусматривает и асинхронный, и синхронный режимы обмена, COM-порт компьютера поддерживает только асинхронный режим. Для реализации синхронного обмена применяются специальные адаптеры, например, SDLC или V.35.
Далее мы опишем основные протоколы последовательной связи, но сначала несколько замечаний.
Слово "протокол” вносит некоторую путаницу. С одной стороны, RS-232 и RS-485 называют протоколами, а с другой, MODBUS, ZModeni и CAN — тоже протоколы. Какой же из них настоящий? Дело в том, что последовательная связь состоит из двух составляющих — аппаратной и программной.
Стандарты RS-232, RS-485 и другие описывают аппаратную часть: разъемы, назначение сигналов, уровни напряжений и т. п. Вторая составляющая —— программная реализация протоколов, т. е. договоренность о правилах передачи данных. В нашей книге мы не будем подробно рассматривать аппаратные реализации всех интерфейсов. Исключение составит описание разъемов, сигналов и соединительного кабеля, необходимого нам для работы. А вот токовая петля, тонкости организации сетей RS-485 и подробное описание различий между RS-протоколами останутся за рамками книги. Интерфейс RS-485 удостоится отдельного рассмотрения из-за необходимости дополнительных программных решений при его реализации.
Вторую составляющую протокола — программную реализацию ——мы будем рассматривать в гл. 2, а создание программ отложим до второй части книги.
И еще одно замечание. Все стандарты имеют двойные, а то и тройные названия. Двойственность названий связана с тем, что Международный союз электросвязи ITU-T использует аналогичные стандарты с названиями V.xx. И, вообще говоря, два названия это еще не предел. Так, стандарту RS-232 соответствует JSO 2110, Министерство обороны США выпустило практически идентичный стандарт Mil-Std-188C, а в нашей стране подобный стандарт введен ГОСТом 18145-81.
Для краткости мы будем использовать два специальных обозначения:
口 DTE (Data Terminal Equipment) — оконечное оборудование, принимающее или передающее данные. В качестве DTE может выступать компьютер, принтер, плоттер или другое периферийное оборудование;
□ DCE (Data Communications Equipment) — аппаратура канала данных. Функция DCE состоит в обеспечении возможности передачи информации между двумя или большим числом DTE. Для этого DCE должно обеспечить соединение с DTE, с одной стороны, и с каналом передачи — с другой. Роль DCE чаще всего выполняет модем, например, как показано на рис. 1.1.
Устройства DTE могут быть соединены напрямую, без DCE, с помощью нуль-модемного кабеля, как показано на рис. 1.2. Разводка такого кабеля показана на рис. 1.3.
(DTE)(DTE)Рис. 1.1. Полная схема соединения по RS-232Рис. 1.1. Полная схема соединения по RS-232(DTE)(DTE)Рис. 1.2. Соединение no RS-232 нуль-модемным кабелемРис. 1.2. Соединение no RS-232 нуль-модемным кабелемРис. 1.3. Нуль-модемный кабель: а ——минимальный; б ——полныйРис. 1.3. Нуль-модемный кабель: а ——минимальный; б ——полный
(DTE)
(DTE)
Рис. 1.1. Полная схема соединения по RS-232
Рис. 1.1. Полная схема соединения по RS-232
(DTE)
(DTE)
Рис. 1.2. Соединение no RS-232 нуль-модемным кабелем
Рис. 1.2. Соединение no RS-232 нуль-модемным кабелем
Рис. 1.3. Нуль-модемный кабель: а ——минимальный; б ——полный
Рис. 1.3. Нуль-модемный кабель: а ——минимальный; б ——полный
Протокол RS-232
Из-за простоты и низких аппаратных требований (в сравнении, например, с параллельным интерфейсом), последовательные интерфейсы активно используются в электронной промышленности. В настоящее время наиболее распространенным является стандарт, разработанный Ассоциацией промышленных средств связи (TIA, Telecommunication Industry Association, http://www.tiaonline.org) и Ассоциацией электронной промышленности (EIA,
Electronic Industries Alliance, http://www.eia.org) ”Е1А/Т1А-232-Е”. более известный под названием ”RS-2321
Стандарт RS-232 (его официальное название 'Interface Between Data Terminal Equipment and Data Circuit-Termination Equipment Employing Serial Binaiy Data Interchange**) предназначен для подключения аппаратуры, передающей или принимающей данные, к оконечной аппаратуре каналов данных. Стандарт описывает управляющие сигналы интерфейса, пересылку данных, электрический интерфейс и типы разъемов.
Интерфейс RS-232 используется и во многих устройствах обычного персонального компьютера, начиная с "мьн丄ш" н модема до ключей аппаратной защиты. И хотя уже все компьютеры имеют интерфейс USB, интерфейс RS-232 еше жив и активно применяется.
Согласно стандарту RS-232, сигнал (поеледовательность битов) передается напряжением. Переда!чик и приемник являются несимметричными', сшнап передается относительно общего провода (в отличие от симметричной передачи протокола RS-485 или RS-422). В табл. I.l приведены границы напряжений для сигналов приемника и передатчика. Логическому нулю на входе приемника соответствует диапазон +3...+ 12 В, а логической единице —— диапазон —12…一3 В. Диапазон —3…+3 В — зона нечувствительности, обеспечивающая гистерезис приемника (передатчика). Уровни сигнала на выходах должны быть в диапазоне —12…—5 В для представления логической единицы и +5...+ 12 В для представления логического нуля.
Вообще говоря, стандарт RS-232 состоит из трех частей. Первая часть, стандарт RS-232C, была принята в 1969 г оду и содержит оп и сан ие электрических цепей и сигналов несимметричной поеледовательной связи. Вторая часть, стандарт RS-232D, принята в 1987 году и определяет дополнительные линии тестирования, а также формально описывает разъем DB-25. Третья часть, RS-232E, принята в 1991 году.
Таблица 1.1. Границы напряжений COM-порта (стандарт RS-232)
Диапазон напряжения входа приемника
Диапазон напряжения выхода передатчика
Состояние управляющего сигнала
Состояние линии данных
Логический 0
от -12 до -3 В
от —12 до -5 В
ON
MARX
Логическая 1
от +3 до +12 В
0T +5 до +12 В
OFF
SPACE
Важно!
Интерфейс не обеспечивает гальванической развязки устройств. Подключение и отключение интерфейсных кабелей устройств с независимым питанием должно производиться при отключенном питании.
Один из самых неприятных недостатков стандарта RS-232 — плохая помехозащищенность и, соответствен но, короткие линии передачи. Естественно, были созданы стандарты, решающие эти проблемы.
Протокол RS-422A
Стандарт RS-422A (другое название ITU-T V.l 1) определяет электрические характеристики симметричного цифрового интерфейса. Он предусматривает работу на более высоких скоростях (до 10 Мбит/с) и больших расстояниях (до 1000 м) в интерфейсе DTE-DCE. Для его практической реализации, в отличие от RS-232, требуются два физических провода на каждый сигнал. Реализация симметричных цепей обеспечивает наилучшие выходные характеристики.
Подобно V.28, данный стандарт является простым описанием электрических характеристик интерфейса и не определяет параметры сигналов, типы разъемов и протоколы управления передачей данных. Для линий интерфейсов RS-422A и RS-423A могут быть использованы различные проводники (или пары проводников) одного и того же кабеля.
Стандарт RS-422A был разработан совместно с RS-423A и позволяет размещать линии этих интерфейсов в одном кабеле. Он несовместим с RS-232, и взаимодействие между RS-422A и RS-232 может быть обеспечено только при помощи специального интерфейсного конвертера.
Практически это полнодуплексный протокол RS-485. Прием и передача идут по двум отдельным парам проводов, причем на каждой паре может быть только по одному передатчику.
Протокол RS-423A
Стандарт RS-423A (другое название V.6) определяет электрические характеристики несимметричного цифрового интерфейса. “Несимметричность” означает, что данный стандарт, подобно RS-232, для каждой линии интерфейса использует только один провод. При этом для всех линий используется единый общий провод.
Как и RS-422A, этот стандарт не определяет сигналы, конфигурацию выводов или типы разъемов. Он содержит только описание электрических характеристик интерфейса. Стандарт RS-423A предусматривает максимальную скорость передачи 100 Кбит/с.
Протокол RS-485
Полное название этого стандарта TIA/EIA-485A (аналогичный стандарт описывается в ISO 8482). Он включает в себя 17 страниц и определяет требования к электрическим характеристикам формирователей и приемников.
В нем ничего не говорится ни о качестве сигнала, ни о методах доступа к линиям связи, ни о протоколе обмена, ни о технологической схеме (назначении контактов и т. п.). "Приложение А” этого стандарта описывает принципы работы устройств на основе RS-485.
В дополнение к описанию стандарта был опубликован документ TSB89, называющийся ''Application Guidelines for TIA/EIA-485A”. TSB89 состоит из 23 страниц и посвящается разъяснению, как применять устройства, определяемые стандартом TIA/E1A-485A для физических сетей.
Подробно этот интерфейс будет обсуждаться в гл. 16.
Протокол RS-499
Стандарт RS-449, в отличие от RS-422A и RS-423A, содержит информацию о параметрах сигналов, типах разъемов, расположении контактов и т, п. В этом отношении RS-449 является дополнением к стандартам RS-422A и RS-423A. Стандарту RS-449 соответствует международный стандарт V.36.
Комбинация RS-449, RS-422A и (или) RS-423A первоначально предназначалась для замены RS-232. Однако этого не произошло, хотя данные стандарты нашли достаточно широкое применение в качестве высокоскоростного интерфейса DTE-DCE.
Стандарт RS-449 определяет 30 сигналов интерфейса, большинство из которых имеют эквиваленты в RS-232, но добавлены и новые. Обозначения большинства сигналов были изменены во избежание путаницы.
Десять сигналов RS-449 определены как линии первой категории. Эта группа сигналов включает в себя все основные сигналы данных и синхронизации, такие как нПередаваемые данные**, "Принимаемые данные**, нСинхро- низация терминала*. Скорость передачи сигналов первой категории существенно зависит от длины кабеля. Для линий этой категории на скоростях до 20 Кбит/с могут использоваться стандарты RS-422A либо RS-423A; на скоростях выше 20 Кбит/с (до 2 Мбит/с) — только RS-422A.
Оставшиеся 20 линий классифицируются как линии второ访 категории и используются стандартом RS-423A. К этой категории относятся такие управляющие линии, как "Качество сигнала**, "Выбор скорости передачи*' и др.
Стандарт RS-449 определяет тип разъема и, в отличие от RS-232, распределение контактов разъема. Используемые разъемы имеют 37 контактов для прямого и 9 контактов для обратного канала.
Протокол RS-562
Протокол RS-562 представляет собой низковольтную реализацию RS-232 с максимальным уровнем сигнала 3,5 В.
Протокол V.24
Рекомендация V.24 содержит описание линий и набора сигналов обмена между DTE и DCE. В RS-232 используются другие обозначения линий, однако линии интерфейса RS—232 и рекомендации V.24 выполняют совершенно одинаковые функции. V.24 определяет большее количество линий, чем RS-232, поскольку стандарт V.24 используется и в других интерфейсах. В этом смысле RS-232 является подмножеством V.24. Рекомендация V.24 не определяет электрические характеристики или другие физические аспекты реализации, такие как тип разъема, расположение контактов, длина кабеля и скорость обмена. Технические вопросы реализации интерфейса подробно изложены в стандарте V.28.
Протокол V.28
Рекомендация V.28 определяет только электрические характеристики интерфейса V.24, обеспечивающего работу по несимметричным двухполярным линиям обмена на скоростях до 20 Кбит/с. К таким характеристикам относятся уровни используемых сигналов, емкостное сопротивление и т. д. Данная рекомендация не содержит требований к длине кабеля, типу разъемов и расположению их контактов. Поэтому рекомендация V.28 может рассматриваться как подмножество стандарта RS-232.
Протокол V.35
Стандарт V.35 появился в начале 1980-х годов как спецификация интерфейса между устройствами доступа к сети (мультиплексором, модемом или др.) и высокоскоростной сетью с коммутацией пакетов. Первоначально эта спецификация использовалась для подключения групповых модемов (модемных пулов) к коммутационному устройству.
Рекомендация V.35 определяет синхронный интерфейс для работы по аналоговым широкополосным каналам с полосой пропускания 60—108 кГц и скоростью передачи до 48 Кбит/с.
В приложении к стандарту определяется вид электрического соединения, обеспечивающего высокоскоростной последовательный интерфейс между мультиплексором и коммутационным оборудованием сети.
Модемы, использующие стандарт V.35, не получили широкого распространения на рынке, но интерфейс в качестве высокоскоростной замены RS-232 прижился. В спецификации стандарта не был определен тип электрического разъема, но фирма IBM в свое время стала выпускать совместимые с V.35 большие прямоугольные разъемы с массивными прижимными винтами. Получилось очень надежное соединение. Остальные производители коммутационной техники стали повторять конструкцию соединителя IBM, который и стал стандартом де-факто, и был принят в качестве рекомендации ISO 2593.
Протокол Х.21
Стандарт Х.21 впервые был опубликован в 1972 году. Он определяет физические характеристики и процедуры управления для интерфейса DTE-DCE в режиме синхронной передачи данных и может применяться как в сетях с коммутацией каналов, так и в сетях на выделенных линиях. Стандарт предусматривает дуплексную работу при условии, что устройства связаны друг с другом реальными, а не виртуальными цифровыми линиями связи. Функциональные процедуры Х.21 формализованы в виде диаграмм состояний, рассмотрение которых выходит за рамки данной книги.
Рекомендация ITU-T Х.21 определяет формат передаваемых символов, которые представляются в коде МТК-5 (Межлународный телеграфный код № 5). Данный интерфейс рассчитан на сквозную цисуровую передачу. В нем процесс установления соединения и разъединения полностью автоматизирован при помощи набора сигналов о состоянии соединения и о его неисправностях. В ходе передачи данных через интерфейс могут передаваться любые последовательности битов.
Создатели этого стандарта стремились максимально упростить его. Так, соединение DTE с DCE требует существенно меньшего числа сигнальных линий, чем аншюгичпое соединение для интерфейса RS-232.
Рекомендация Х.21 bis
Рекомендация X.2lbis была разработана для обеспечения возможности подключения к сетям передачи данных общего пользования тех пользователей, которые используют для этого аналоговые выделенные или ком мутируемые каналы и имеют синхронные модемы, работающие согласно рекомендациям серии V.
Краткое сравнение RS-протоколов
В табл. 1.2 мы привели краткое сравнение 4 наиболее распространенных RS-протоколов. Более подробную информацию можно найти в соответствующей литературе.
Таблица 1.2. Сравнение последовательных интерфейсов
RS-232CV.24
RS-422AV.11
RS-423AV.6
RS-485
Максимальная длина кабеля, м
15
1200
1200
1200
Число приемников
1
10
10
32
Число передатчиков
1
1
1
32
Таблица 1.2 (окончание)
RS-232CV.24RS-422AV.11RS-423AV.6RS-485
Максимальная ско- 20 Кбит/с 100 Кбит/с 10 Мбит/с 10 Мбит/с рость передачи
Принципы последовательной связи
При асинхронной поеледовательной связи (напомним, что именно асинхронный интерфейс реализован в IBM PC) одно из устройств посылает или принимает байты информации по одному биту. Интервалы времени между байтами при этом несущественны, но времена между отдельными битами байта очень важны. Сигнал на линии может быть высокого или низкого уровня, что соответствует логическим пулю (SPACE) и единице (MARK). Линия поддерживается в отмеченном состоянии, когда по пей нет передачи данных. При начале передачи байта данных сигнал падает в 0, отмечая стартовый бит. Затем следуют биты данных (от 5 но 8) в виде набора высоких и низких уровней. Последний бит данных может сопровождаться битом четности, используемым для обнаружения ошибок, а затем в последовательность включаются один или более стоп-битов, которым соответствует высокий уровень. Эти стоп-биты начинают отмеченное состояние, которое будет сохраняться до тех пор, пока не начнется передача следующего байта данных. Число используемых стоп-битов существенно, поскольку они устанавливают минимальное время, которое должно пройти перед следующим стартовым битом (рис. 1.4).
Рис. 1.4. Передача байта с помощью последовательности битов
Конечно, передающая и приемная станшш должны использовать один и тот же протокол для этих цепочек битов и они должны работать с одной и той же скоростью обмена (измеряемой в битах в секунду, называемых также бодами).
Разъемы коммуникационного порта
Кроме электрических характеристик, RS-стандарт регламентирует разъемы, используемые для соединения устройств (рис. 1.5).
(6) DSR4(7) RTS、(8) CTS、(9) R、(6) DSR4(7) RTS、(8) CTS、(9) R、/(1) DCD /⑵RD /⑶TD / (4) DTR / (5) PG/SG/(1) DCD /⑵RD /⑶TD / (4) DTR / (5) PG/SG◎◎◎◎(20) DTR、(20) DTR、(22) RI 、(22) RI 、/⑴PG /⑵TD /⑶RD / ⑷ RTS / (5) CTS / (6) DSR/ (7) SG / (8) DCD/⑴PG /⑵TD /⑶RD / ⑷ RTS / (5) CTS / (6) DSR/ (7) SG / (8) DCD
(6) DSR4
(7) RTS、
(8) CTS、
(9) R、
(6) DSR4
(7) RTS、
(8) CTS、
(9) R、
/(1) DCD /⑵RD /⑶TD / (4) DTR / (5) PG/SG
/(1) DCD /⑵RD /⑶TD / (4) DTR / (5) PG/SG
◎
◎
◎
◎
(20) DTR、
(20) DTR、
(22) RI 、
(22) RI 、
/⑴PG /⑵TD /⑶RD / ⑷ RTS / (5) CTS / (6) DSR
/ (7) SG / (8) DCD
/⑴PG /⑵TD /⑶RD / ⑷ RTS / (5) CTS / (6) DSR
/ (7) SG / (8) DCD
Рис. 1.5. Разъемы DB-9 и DB-25,используемые в СОМ-портах компьютера
На СОМ-портах компьютера используются два типа разъемов — DB-25P и более компактный вариант DB-9P. На аппаратуре используются разъемы DB-25S или DB-9s. Соответствие сигналов 9- и 20-штырьковых разъемов и их описания приводятся в табл. L3.
Таблица 1.3. Сигналы интерфейса RS-232
Контакт
Обозначение
Описание
Аббревиатура
I/O
DB-25
DB-9
EIA (RS-232)
ITU-T (V.24)
1
АА
Защитное заземление
GND
2
3
ВА
103
Передаваемые данные
TxD (TD)
о
Таблица 1.3 (продолжение)
Контакт
Обозначение
Описание
Аббревиатура
I/O
DB-25
DB-9
EIA (RS-232)
1TU-T (V.24)
3
2
ВВ
104
Принимаемые данные
RxD (RD)
I
4
7
CACJ
105
133
Запрос передачи
Готовность к приему
RTS
О
5
8
СВ
106
Готовность к передаче
CTS
о
6
6
СС
107
Готовность DCE
DSR
I
7
5
АВ
102
Сигнальное заземление
SG
I/O
8
1
CF
109
Обнаружение несущем
DCD
I
9
Резерв для теста DCE, 十12 В
I
10
Резерв для теста DCE, -12В
I
11
126
Выбор частоты передачи
о
12
SCF
122
Обнаружение несущем дополнительного канала
SDCD
I
13
SCB
121
Готовность к передаче по дополнительному каналу
SCTS
I
14
SBA
118
Передаваемые данные дополнительного канала
STD
0
15
DB
114
Синхронизация передачи
TC
I
16
SBB
119
Принимаемые данные дополнительного канала
SRD
I
17
DD
115
Синхронизация приема
RC
I
18
LL
141
Свободный (местный шлейф)
о
19
SCA
120
Запрос передачи дополнительного канала
SRTS
о
20
4
CD
108.1
108.2
Готовность DCE
Готовность DTE
DTR
о
о
21
CG
RL
110
140
Детектор качества сигнала
Удаленный шлейф
I
о
Таблица 1.3 (окончание)
Контакт
Обозначение
Описание
Аббревиатура
I/O
DB-25
DB-9
EIA (RS-232)
ITU-T (V.24)
22
9
СЕ
125
Индикатор вызова
RI
I
23
сн
CI
111
112
Переключатель скорости передачи данных (DTE)
Переключатель скорости передачи данных (DCE)
О
I
24
DA
113
Синхронизация передачи (DTE)
о
25
ТМ
142
Свободный (индикатор тестирования)
I
Все линии обмена между DTE и DCE можно разбить на четыре основные группы:
口 линии данных;
口 линии управления;
линии синхронизации;
линии "земли".
Далее мы приведем краткое описание сигнсыов. Подробное описание выходит за рамки книги по програм м и ро ван и ю.
Сигнальная "земля" (AB/SG)
Эта линия является общим проводом для всех электрических цепей, образуемых линиями физического интерфейса. Стандарт рекомендует присоединять этот общий провод к защитной "земле” путем внутреннего соединения в DCE. Смысл такого соединения заключается в том, что корпуса устройств оказываются заземленными через штепсельную розетку.
Защитная "земля" (АА)
Эта линия присутствует только в интерфейсе с разъемом DB-25 и предполагает соединение с корпусом устройства.
Передаваемые данные (BA/TxD/TD)
Сигналы, которые присутствуют на этой линии, вырабатываются местным (локальным) DTE для передачи местному DCE. Посылаемые сигналы могут быть кодами команд, управляющих работой местного DCE (АТ-команды или другие) или данными, которые местное DCE должно передать удаленному DC Е-устройству.
Если DTE не передает данные, то оно удерживает эту линию в состоянии логической единицы (MARK). Согласно стандарту, DTE не будет передавать данные до тех пор, пока управляющие линии "Запрос передатчика", "Сброс передатчика", *Тотовность DCE" и "Готовность DTE” не будут находиться одновременно в активном (ON) состоянии.
Независимо от того, относится ли данное устройство к DTE или DCE, рассматриваемая линия всегда называется одинаково: 'Передаваемые данные". Это выходная линия для DTE и входная для DCE.
Принимаемые данные (BB/RxD/RD)
Сигнгты, которые присутствуют на этой линии, вырабатываются местным (локальным) DCE для передачи местному DTE. Передаваемые сигналы могут быть ответами на команды или данными, получаемыми от удаленного DCE.
Если не выполняется операция подтверждения приема команд 1Я, стандартное DCE удерживает эту линию в состоянии логической единицы (MARK) при условии, что линия "Указатель несушей" находится в неактивном состоянии (OFF).
При полудуплексной работе эта линия удерживается в состоянии MARK, когда линия “Запрос передачи*1 находится в активном состоянии, а также в течение короткого промежутка времени после ее перехода из активного состояния в неактивное.
Независимо от того, относится ли данное устройство к DTE или DCE, рассматриваемая линия всегда называется одинаково: "Принимаемые данные". Это выходная линия для DCE и входная для DTE.
Запрос передачи (CA/RTS)
Сигналы на этой линии вырабатывает DTE. В симплексных или дуплексных системах активное состояние этой линии обеспечивает удержание DCE в режиме передачи. Переключение в неактивное состояние п ри останавл и вает передачу. В обоих случаях состояние этой линии никак не влияет на работу DCЕ-устройства как приемника.
В полудуплексных системах переключение этой линии в активное состояние переводит DCE в режим передачи и приостанавливает его работу на прием. Когда DTE переключает эту линию в неактивное состояние, соответствующее DCE-устройство начинает работать в режиме приема.
Если DTE переключило линию "Запрос передачи1* в неактивное состояние, оно не должно снова активизировать эту линию до тех пор, пока DCE- устройство не подтвердит прием этого сигнала путем переключения в такое же неактивное состояние линии "Готовность к передаче1*.
Переключение линии "Запрос передач и1* из неактивного в активное состояние является сигналом на переход DCE в режим передачи. DCE может затем выполнять любые действия, необходимые для подготовки к передаче, и после их завершения устанавливает линию "Готовность к передаче'* в активное состояние, сообщая тем самым, что DCE может передавать данные.
Переключение линии "Запрос передачи" из активного в неактивное состояние является сигналом для DCE на завершение обработки любых данных, которые уже получены от DTЕ-устройства. Затем DCE прекращает передачу или переходит в режим приема. О завершении этого процесса оно сообщает путем переключения линии "Готовность к передаче1' в неактивное состояние.
Готовность к передаче (CB/CTS)
Сигналы на этой линии вырабатывает DCE. Эти сигналы сообщают о готовности DCE к приему данных от связанного с ним DTE-устройства. Если линия 'Тотовность к передаче" находится в неактивном состоянии, DTE не должно передавать данные. Когда DCE переключает эту линию в активное состояние, оно готово принимать данные. Эти данные могут быть командами для DCE или данными, передаваемыми по каналу связи.
Обычно сигнал "Готовность к передаче" является ответом на сигнал "Запрос передачи”. Однако DCE может независимо переключить линию "Готовность к передаче11 в неактивное состояние, чтобы сообщить DTE о необходимости приостановки передачи данных на некоторый конечный промежуток времени. Любые данные, переданные после переключения линии “Готовность к передаче*1 в неактивное состояние, могут быть проигнорированы DCE-устройством. DCE может снова активизировать эту линию в любой момент при условии, что линия “Запрос переда”Г' также находится в активном состоянии. Такая процедура называется "аппаратное управление потоком данных1*.
Если линия "Запрос передачи" не используется, DCE будет работать так, будто эта линия все время находится в активном состоянии.
Готовность DCE (CC/DSR)
DCE использует эту линию для информироваиия DTE о своей готовности к работе. Для соответствующего сигнала часто используется название: *Тотов- ность устройства сопряжения" или *'Готовность модема". Активное состояние линии означает, что DCE готово обмениваться информацией с DTE и начать передачу данных.
В некоторых реализациях данная линия, в комбинации с линией "Индикатор тестирования'*, используется для управления обменом сигналами при тестировании и обслуживании DCE. В других случаях эта линия используется вместе с линией "Готовность к передаче*1 для управления и программирования DCE, поддерживающего последовательную систему автоматического вызова.
Готовность DTE (CD/DTR)
Сигналы на этой линии вырабатывает DTE. Переключение этой линии в активное состояние информирует DCE-устройство о том, что ему нужно приготовиться к соединению с каналом связи. Если DCE может автоматически отвечать на последующие вызовы, оно будет делать это только в том случае, если линия "Готовность DTE" находится в активном состоянии. Состояние данной линии не влияет на сигналы, присутствующие на линии "Индикатор вызова1*.
Если текущее соединение с каналом связи установлено, то активное состояние линии 'Тотовность DTE" указывает, что DCE должно поддерживать это состояние. Если эта линия впоследствии переключается в неактивное состояние, DCE отключится от канала связи после завершения текущей передачи данных. После перехода в неактивное состояние линия ''Готовность DTE" не должна активизироваться снова до тех пор, пока от DCE не будет получено подтверждение этого перехода путем переключения линии "Готовность DCE** в неактивное состояние.
Индикатор вызова (CE/RI)
DCE использует эту линию для сообщения о том, что по каналу связи принимается сигнал вызова. Сигнал на линии "Индикатор вызова*1 соответствует состоянию сигнала вызова ON при наличии сигнала вызова, и состоянию OFF — при его отсутствии. Эта линия всегда активна. Однако DTE может игнорировать этот сигнал по своему усмотрению.
Обнаружение несущей (CF/DCD)
DCE активизирует эту линию при получении сигнала, служащего указателем возможности установления соединения с подходящим качеством связи.
Если линия находится в неактивном состоянии, то это означает либо полное отсутствие сигнала, либо наличие сигнала неудовлетворительного качества. Какой сигнал считать подходящим по качеству, DCE определяет само.
Если во время передачи данных возникнут обстоятельства, требующие переключения линии "Обнаружение несуще"” в неактивное состояние (означающего потерю несущей), DCE также установит сигнал MARK на линии *'Принимаемые данные".
В полуд у плексных системах данная линия переключается в неактивное состояние всякий раз, когда активизируется линия "Запрос передачи", а также в течение короткого промежутка времени после переключения линии 'Запрос передачи1* из активного в неактивное состояние.
Детектор качества сигнала (CG/SQ)
Использование этой линии в настоящее время не рекомендуется.
Переключатель скорости передачи данных от DTE (СН)
По этой линии DTE сигнализирует о том, какую из двух возможных скоростей передачи данных или какой диапазон скоростей передачи должно выбрать DCE. Активное состояние этой линии соответствует выбору более высокой скорости передачи.
Переключатель скорости передачи данных от DCE (CI)
По этой линии DCE сообщает о том, какую из двух возможных скоростей передачи данннх или какой диапазон скоростей передачи оно выбирает. Активное состояние этой линии соответствует выбору более высокой скорости передачи.
Готовность к приему (CJ)
Для обеспечения документированного метода аппаратного управления потоком данных стандартом RS-232 предусмотрена линия "Готовность к приемун. DTE активизирует эту линию, чтобы сообщить DCE о своей готовности к приему данных.
Напротив, неактивное состояние этой линии означает, что DTE не может принимать данные от DCE. В этом случае DCE должно сохранить не переданные данные. Локальное DCЕ-устройство может передать удаленному DCE сигнал на приостановку передачи данных по каналу связи.
В системах, использующих линию 'Тотовность к приему*1, все остальные линии работают так, как если бы линия "Запрос передаин” постоянно находилась в активном состоянии.
Местный шлейф (LL)
DTE использует эту линию для перевода локального DCE в режим петлевого тестирования. Когда DTE активизирует линию ° Местный шлейф", локальное DCE-устройство отключает свой сигнальный выход от канала связи и подключает его к своей собственной входной линии. Затем это DCE активизирует линию *'Индикатор тестированияп. В результате этого любые данные, передаваемые от DTE к DCE, немедленно возвращаются обратно к DTE. При переключении линии "Местный шлейф" в неактивное состояние DCE ре конфи гурирует себя для нормальной работы. Состояние линии "Местный шлейф" не влияет на работу линии "Индикатор вызова**.
Удаленный шлейф (RL)
DTE использует эту линию для перевода удаленного DCE в режим дистанционного тестирования. Когда DTE активизирует линию "Удаленный шлейф", локальное DCE выдает команду удаленному DCE на установку петлевой конфигурации. Когда установка такой конфигурации завершена, локальное DCE переключает линию "Индикатор тестирования** в активное состояние.
При дистанционном тестировании данные, передаваемые локальным DTE-устройством, проходят через локальное DCЕ-устройство и далее поступают в канал связи. Удаленное DCE принимает эти данные и сразу же передает их обратно по каналу связи к локальному DCE, а последнее ——к локальному DTE. Когда локальное DTE переключает линию "Удаленный шлейф** в неактивное состояние, локальное DCE выдает команду удаленному DCE на окончание тестирования.
Во время дистанционного тестирования умаленное DCE устанавливает линию "Готовность DCE" в неактивное, а линию мИндикатор тестнрованкзТ' в активное состояние, указывая тем самым, что связь с удаленным DCE невозможна.
Индикатор тестирования (ТМ)
DCE активизирует эту линию для того, чтобы сообщить DTE о своем переходе в тестовый режим. Активизация этой линии является откликом DCЕ-устройства на переключение линий "Местный шлейф" или "Удаленный шлейф'1 в активное состояние. Линия '*Индикатор тестирования" активизируется также в том случае, когда DCE отвечает на команду перехода в режим петлевого тестирования, поступающего от удаленного DCE. Неак-
тивное состояние линии "Индикатор тестирования" означает, что DCE готово для нормальной работы.
Синхронизация передачи от DTE (DA)
По этой линии DTE передает сигналы для синхронизации DCE. Моменты переключения этой линии из активного состояния в неактивное номинально соответствуют середине каждого элементарного сигнала (импульса), поступающего от DTE на линию "Передаваемые данные'*. Если эта линия реализована в интерфейсе, то для поступления на нее синхронизирующей информации обычно достаточно, чтобы DTE-устройство находилось во включенном состоянии.
Синхронизация передачи от DCE (DB/TC)
По этой линии DCE передает сигналы для синхронизации DTE. DCE должно выдавать элементарные сигналы на линию "Передаваемые данные'* таким образом, чтобы значащие моменты переходов между соседними элементарными сигналами (битами) соответствовали моментам переключения линии DB из неактивного состояния в активное.
Синхронизация приема
от DCE (DD/RC)
По этой линии DCE передает сигналы для обеспечения синхронной работы DTE в режиме синхронной передачи данных. Моменты переключения этой линии из активного состояния в неактивное соответствуют середине каждого элементарного сигнала (бита), поступающего от DCE на линию “Прн- нимаемые данные*1.
Передаваемые данные дополнительного канала (SBA/STD)
Эта линия эквивалентна линии "Передаваемые данные11, но используется для организации дополнительного канала связи.
Принимаемые данные дополнительного канала (SBB/SRD)
Эта линия эквивалентна линии "Принимаемые данные", но используется для организации дополнительного канала связи.
Запрос передачи
по дополнительному каналу (SCA/SRTS)
Эта линия эквивалентна линии "Запрос передачи'*, но используется для организации доп ол н ител ьн о го канала связи.
Готовность к передаче по дополнительному каналу (SCB/SCTS)
Эта линия эквивалентна линии 'Тотовность к передаче", но используется для организации дополнительного канала связи.
Обнаружение несущей дополнительного канала (SCF/SDCD)
Эта линия эквивалентна линии "Обнаружение несущей", но используется для организации дополнительного канала связи.
1.4. Ресурсы 旧М PC
для последовательной связи
Реализация последовательной связи возможна на нескольких 'уровнях'*:
низкоуровневый доступ к портам:
использование сервисов BIOS;
прямое программирование портов;
использование прерываний;
операционная система Windows:
прямое программирование портов:
использование потоков;
использование событий.
Сервисы BIOS
Компьютер может иметь до четырех п ос л ед о вате л ьн ых портов COM I — COM4 (для обычных компьютеров типично наличие двух портов) с поддержкой на уровне BIOS. Однако сервис BIOS (INT14H) обеспечивает работу7 с портом только на скоростях 110——9600 бит/с, что чаще всего значительно меньше реально возможной скорости. Для обхода этой проблемы используется прямое программирование комм ун и каци онно го порта.
Мы рассмотрим использование сервисов BIOS в гл. 6. Эта информация может пригодиться тем, кто вынужден поддерживать чужие программы, написанные с использованием этих сервисов. Кроме того, промышленные компьютеры с встроенным клоном DOS обычно предоставляют именно такой сервис для последовательной связи с внешними устройствами (справедливости ради, отметим, что DOS-клоны промышленных компьютеров имеют функцию INT14H с кодом 80Н для работы на скоростях 9600—115200 бит/с).
Справочная информация о функциях BIOS для последовательных портов приводится в гл, 18.
Коммуникационные порты
Ком мун и ка цион н ые порты занимают в пространстве ввода/вывода по восемь смежных 8-битовых регистров и могут располагаться по стандартным базовым адресам. Прямое программирование портов предоставляет полный доступ к возможностям UART микросхемы.
Эта информация может пригодиться не только тем, кто пишет программы для микроконтроллеров для DOS, но и использующим Windows DDK, т. к. любые программные надстройки базируются на возможностях аппаратуры.
Прямым программированием портов мы займемся в г/?. 7, а полное описание портов и их регистров можно найти в 乙九 19.
Использование прерываний
Порты могут генерировать аппаратные прерывания. Это позволяет создавать свои обработчики прерывгший, вызывающиеся только при возникновении некоторых условий (например, при получении нового символа или возникновении ошибок приема).
Использование прерываний максимально использует ресурсы асинхронной микросхемы при программировании поеледовательной связи на низком уровне. Программу, использующую прерывания, мы создадим в а/. 8. а полное описание ресурсов контроллера прерываний приводится в zz. /9.
Прямое программирование
портов в Windows
Windows 95/98 не блокируют прямой доступ к портам. В гл. 9 мы покажем, как получить доступ к портам в Windows 95/98, а также как обойти запрет на доступ к портам в Windows NT/2000/XP.
Эта информация необходима программистам, занимающимся поддержкой существующих программ для Windows 95/98 или желающим перенести имеющийся код в Windows 2000 без серьезных изменений.
Более того, мы опишем метод, с помощью которого можно будет запустить любой скомпилированный файл, работающий с портами напрямую в операционных системах Windows NT/2000/XP
Функции Windows
Конечно, при написании новой программы для Windows правильнее использовать функции Windows. В гл. 10 мы будем использовать базовые функции Windows. Затем, в гл. //, мы покажем, как использовать потоки, позволяющие существенно разгрузить основной поток программы.
В гл. 12 мы покажем, как использовать функции асинхронного доступа и события.
В гл. /3 мы опишем использование допол 11 и тел ьн ых коммуникационных функций, которые потребуются нам при реализации протоколов обмена, а также функции, добавленные только в Windows 2000/ХР.
Глава 2
Протоколы
В этой главе описываются типы протоколов обмена, методы организации передачи данных и методы повышения достоверности передачи.
Что такое протокол обмена
Чтобы принимающее устройство умело правильно интерпретировать полученные данные, необходима договоренность между приемником и передатчиком ——протокол обмена. Протокол обмена состоит из нескольких частей: 口 договоренность о параметрах связи (скорость, четность, число бит в байте и т. д.);
口 договоренность о последовательности передачи данных и форматах представления чисел;
П договоренность об инициаторе обмена (если необходимо);
П процедура восстановления связи (если необходимо);
口 дополнительные договоренности (например, передача прав главного хоста в сетях RS-485).
Параметры связи обязывают приемник и передатчик работать с одинаковыми аппаратными настройками. Обычно промышленные устройства используют строго определенные настройки обмена, некоторые — позволяют задавать скорость обмена (как программно, так и с помощью перемычек). Настройки четности и число бит в байте чаще всего фиксируются и не могут настраиваться со стороны аппаратуры.
Иногда, для согласования скорости работы, устройства выбирают фиксированную скорость начала обмена, устанавливают соединение и, обмениваясь данными на этой скорости, ,,договариваются,' об использовании других скоростей и параметров обмена.
Для того чтобы принимающее устройство могло воспользоваться полученной информацией, ему необходимо знать, как ее интерпретировать. Для этого приемник и передатчик декларируют поеледовательность передачи данных. Часто именно эту часть договоренностей называют протоколом обмена, подразумевая наличие остальных частей.
При использовании приемником и передатчиком одной линии связи (как, например, в интерфейсе RS-485), необходима договоренность об инициаторе обмена. Это тем более необходимо в тех случаях, когда приемник один, а передатчиков много.
При разрыве связи одно из устройств может ждать ответа другого, а то, в свою очередь, ответа первого. Таким образом, без договоренности о действиях в случае разрыва связи устройства не смогут связаться друг с другом повторно. Часто выбирается очень простой способ — если в течение некоторого времени устройства не получают данных друг от друга, они переходят в режим **первого запуска*'. Однако возможны и более экзотические договоренности.
С точки зрения формата передаваемых данных, типы протоколов обмена можно поделить на две категории ——строковые и бинарные. В первом случае информация передается обычными ASCII-символами (American Standart Code for Informational Interchange, американский стандартный код для обмена информацией), а во втором — бинарным потоком данных. Далее мьг рассмотрим оба эти типа, а их реализацией займемся в гл. 14.
ASCII-протокол передачи данных
В таком протоколе управляюшие символы и данные передаются с помощью ASCII-символов.
Пример пакета данных в таком формате приведен в табл. 2.1. Пакет данных начинается со стартовой поеледовательноспi ——одного или нескольких символов, обозначающих начало данных. Символы стартовой последовательно- сти не должны появляться внутри блока данных. Получив байты начала посылки, все абоненты настраиваются на получение полного пакета данных. Затем, получив и сравнив номер абонента со своим номером, абонент либо продолжает обработку пакета, либо снова переходит в режим ожидания начала посылки. Таким образом, пакет обрабатывает только один абонент сети. После получения байта конца посылки абонент проверяет контрольную сумму и обрабатывает полученные данные. Конец посылки также может представляться несколькими символами. Разумеется, строка конца посылки не должны встречаться внутри самих данных.
Естественно, если связь осуществляется между двумя абонентами, поле “номер абонента” может отсулствовать.
Байт
ОписаниеПримеры
Начало посылки
Идентификатор начала посылки. Любой сим-$!!!
вол (или несколько символов), не встречающийся в символах данных
Номер абонента
Две десятичные цифры номера абонента (до-0115
полненные при необходимости нулем). В некоторых случаях могут оговариваться специальные номера абонентов, например, означающая посылку всем абонентам сети
Данные
Последовательность цифр данных. Числа с 011 03.1155 17.5
плавающей точкой разделяются знаком "「.
Части данных могут разделяться пробелом
Контроль
Контрольная сумма данных. Обычно один или002104
два байта суммы всех байт данных с переполнением. Байты суммы также передаются в десятичном виде (три десятичные цифры с выравниванием нулями слева)
Конец посылки
Идентификатор конца посылки. Для ASCII-про-$0D***
токолов обычно символ LR (перевод строки)
Достоинство такого протокола в удобстве отладки: прием и передачу команд и данных можно осуществлять с помощью обычной программы-терминала (например, Telnet).
Основной недостаток — большой объем данных, необходимых для передачи. Например, для передачи одного байта шестнадцатеричной строкой потребуется два байта (число 230 будет передано последовательностью $Е, $6). А при передаче обычными десятичными цифрами —— три байта (число 230 будет передано тремя цифрами ”2,3,0”). Передача же чисел с плавающей точкой потребует количество байт, пропорциональное точности передаваемого числа (плюс десятичный разделитель). Например, число 221,73 будет передаваться с помощью поеледовательности символов ,,2,2,1,.,7,3*\ т. е. с помощью 6 байт.
Второй недостаток ——необходимость преобразования двоичных данных в ASCII-строки и обратно. Если на языке высокого уровня (С, Pascal) это не проблема, то, например, для 8-битового контроллера может потребоваться значительный объем кода (пример кода, реализующего такое преобразование приводится в 叩%/. 8).
Хорошим примером ASCII-протокола является MODBUS-ASCII (см. разд. 4.1.1)、а программной реализацией своего ASC11-протокола мы займемся в Z7. 74
Бинарный протокол передачи данных
Бинарная передача значительно более компактна. Например, для передачи числа с плавающей точкой типа single (см. прил. 3) требуется 4 байта независимо от числа знаков после десятичной точки.
Так же как в ACS 11 -протокол е, выбирается некоторый символ для обозначения начала посылки. Завершение ожидания данных производится либо по получению необходимого числа байт, либо по тайм-ауту: принимающее устройство отсчитывает время с получения последнего байта до приема следующего. Если эта пауза превышает некоторый интервал (обычно 1,5— 2 байта), посылка считается потерянной и приемный буфер сбрасывается. После получения необходимого числа байт (или символа конца посылки) проверяется правильность полученных данных и производится их обработка. Примером бинарного протокола может служить протокол MODBUS-RTU (см. разд. 4.1.2)* а реализацией своего бинарного протокола мы займемся в гл. 14.
Предотвращение потери данных
Причины потери данных при передаче или приеме можно разделить на две категории: аппаратные и программные.
К аппаратным причинам потери данных относятся помехи на линии, некачественный проводник, несоответствие длины провода и скорости передачи и т. д. Бороться с такими причинами, конечно, надо, но не на программном уровне. Правила прокладки сетей и расчет максимальной длины проводника можно найти в соответствующей литературе, например, см. в |2] и [9]. От программной составляющей в этом случае требуется только определить, что данные были переданы с ошибкой. Способы обнаружения ошибок мы рассмотрим дальше.
Потеря данных может возникать и при неправильной организации программы. Последовательная передача подразумевает, что все биты, посланные с одной стороны, будут приняты с другой. Более того, во время передачи и приема байта нельзя допускать никаких задержек: если байт не считан вовремя, он теряется и его место занимает следующий переданный байт. Аналогично, если хотя бы часть пакета данных будет потеряна, это, чаше всего, означает потерю всего пакета данных.
Прерывания и потоки
Основная причина потери данных ——программа занята обработкой других данных и не опрашивает порт. Возможно, в этот момент идет обработка предыдущего полученного пакета данных или программа занята диалогом с
пользователем. Все эти причины сводятся к одной ——неправильной организации программы.
Рассмотрим, например, алгоритм, приведенный в листинге 2.1.
Листинг^!. Неправильная организация опроса 门орта
Begin
While (True) do begin { Опрос клавиатуры } If KeyPressed then Case ReadKey of
#27: Break; {выход}
*q*: Begin Write('Введите К*); ReadLn(К);
End;
End;
End;
{ Опрос порта } DoReadPort;
{ Есть новые данные! }
If DataAvailable then begin
WriteLn (* Значение температуры: 1, Code JDC * K); DataAvailable:= False;
End;
End;
End.
При всей заманчивой простоте такого кода так делать не надо! Все будет хорошо, пока пользователь не нажмет клавишу <q>, вызывающую диалог ввода нового коэффициента. А вот во время диалога порт опрашиваться не будет. В программе для Windows можно сделать аналогичную ошибку, вызвав метод DoModai формы диалога параметров.
Конечно, В Windows МОЖНО воспользоваться компонентом TTiiner и делать опрос порта в обработчике onTimer с некоторой фиксированной частотой. Однако и этот способ далек от совершенства. Дело в том, что, во-первых, при большой скорости передачи данных буфер приема драйвера порта может переполниться, и часть данных будет потеряна. А, во-вторых, таймер
работает с помощью посылки сообщения wm_timer окну программы. Это сообщение имеет низший приоритет, и вероятность того, что считывание порта будет произведено вовремя, достаточно мала. В Windows 95/98 достаточно, щелкнув на заголовке окна, удерживать кнопку “мыши” в нажатом состоянии — и сообщения wm_timer не будут обрабатываться. Таким образом, если пользователь будет перетаскивать окно на другое место, порт опрашиваться не будет. И еще один недостаток: несмотря на ю, чго редактор свойств позволяет установить свойство interval (частоту вызова обработчика таймера) в любое значение, большее нуля, реально рабогающим минимальным значением будет 70—100 мс.
Раскритиковав все способы работы, стоит предложить какой-нибудь правильный. Для DOS таким способом является обработка прерываний, рассматриваемая в гл. 8. А для Windows правильным способом обработки порта является создание отдельного потока для чтения данных, как будет показано в гл. 7/ и 72
Буферизация
При большой скорости передачи данных, очевидно, может возникнуть ситуация, когда время на обработку полученного пакета данных (например, рисование графика, анализ данных и сохранение их в файл) больше, чем время между получением пакетов.
В таких случаях необходимо некоторое промежуточное хранилище полученных данных — буфер.
Буферизация позволяет основному модулю не заботиться о скорости обработки данных. Конечно, размер буфера тоже ограничен, но обычно его выбирают достаточно большим.
На уровне приема одного байта существуют свои буферы приема:
в DOS можно включить специальный режим буферизации Fl FO;
Windows имеет свои буферы, размер которых можно изменять вызовом функции SetupComm.
Однако чаще всего программе важнее иметь свой буфер, хранящий полные пакеты данных, а системные буферы использовать как дополнительный метод повышения надежности (см. гл. 14).
Единственная сложность буферизации ——необходимость синхронизации процедуры выборки данных из буфера и процедуры добавления в буфер. Синхронизация необходима, т. к. в DOS добавление в буфер будет производиться из прерывания, а в Windows ——из другого потока. В обоих случаях вероятность того, что основная программа будет прервана на обновление буфера в середине процедуры выборки данных, достаточно высока. Очевидно, в таком случае возникнет ошибка.
Синхронизация в DOS
В DOS синхронизация возможна только одним способом — запрещением прерываний на время выборки данных из порта, как показано в листинге 22
Листинг 2.2. Синхронизация в DOS
{Обработчик прерывания} Procedure OnCOMInterrupt(В : Byte); Begin
{ Добавить байт в буфер } AddByteToBuffer(В);
End;
Var
В : Byte;
Begin
{Основной цикл программы}
While (True) d。 begin
{Запретить прерывание}
DisablelRQ;
{Забрать байт, если он есть}
If GetSizeBuffer > 0 then В:= GetByteFromBuffer;
{Разрешить прерывания}
EnablelRQ;
... обработка нового байта В
End;
End.
Обратите внимание, что обработка байта производится вне блока [DisabieiRQ, EnabieiRQ]. Таким образом, запрещение прерываний происходит на очень маленький промежуток времени выборки данных из буфера.
Синхронизация в Windows
В Windows существуют свои методы синхронизации. Для синхронизации двух потоков нужно создать специальный объект синхронизации, например, TCriticaiSection. Скелет такой программы приводится в листинге 2.3.
Листинг 2.3. Пример синхронизации в Windows
Uses SyncObjs; {нужно для использование объектов синхронизации} Var
CS : TCriticalSection;
{Создание объекта синхронизации} procedure TForml.FormCreate(Sender: TObject); begin
CS:= TCriticalSection.Create; end;
{Уничтожение объекта синхронизации} procedure TForml.FormDestroy(Sender: TObject); begin
CS.Free;
end;
{Добавление байта в буфер}
procedure TThreadl.DoAddToBuf fe r(:); begin
CS. Acquire;
Try
:добавить в буфер: Finally
CS. Release;
End;
end;
{Выборка байта из буфера}
procedure TThread2.DoGetByteFroroBuffer(:); begin
CS. Acquire;
Try
:забрать из буфера: Finally
CS. Release;
End;
end;
Подробнее про объекты синхронизации можно прочитать в [111.
Обратная связь
Одной из причин потери данных является занятость приемника, в то время как передатчик продолжает передавать данные. Избежать переполнения можно, организовав обмен таким образом, чтобы приемник мог сообщать в своей занятости передатчику.
Конечно, обратная связь требует уже полного нуль-модемного провода, содержащего все необходимые линии. Система обратной связи может быть либо аппаратной, либо программной.
Аппаратная обратная связь заключается в выделении некоторой линии (например, DTR/DSR) для уведомления передатчика о возможностях приемника. В этом случае передатчику разрешается передавать данные, только пока DSR находится в состоянии ON. Как только приемник заполнил свой буфер, он сбрасывает DTR в OFF, уведомляя передатчик о необходимости прервать передачу данных. Windows позволяет включать такой режим с помощью флага fOutxCtsFiow (управление с помощью CTS) или флага fOutxDsrFiow (управление с помощью DSR).
Программная обратная связь осуществляется посылкой специальных символов. При получении символа хоп передатчик начинает передачу данных, а при получении символа xoff останавливает передачу. В Windows существует режим автоматического управления посылкой таких символов (см. гл. 20).
Методы обнаружения ошибок передачи
Протокол RS-232 имеет существенный недостаток — плохую помехозащищенность. Если для символьного протокола некоторой защитой служит ограничение диапазона самих символов (например, в посылке числовых данных могут встречаться только символы ”0...9”, “.”,"—” и, может быть, буквы), то для бинарного протокола необходимы дополнительные меры контроля данных.
Конечно, аппаратный контроль четности позволяет в некоторой степени контролировать достоверность данных, но, как мы покажем в разд. 10.5. надежность такого способа далека от совершенства.
Способы борьбы с ошибками передачи можно разделить на две категории: не использующие обратную связь и используюишс ее.
В первом случае на передающей стороне передаваемые данные кодируются одним из известных кодов с исправлением ошибок. На приемной стороне, соответственно, производится декодирование принимаемой информации и исправление обнаруженных ошибок. Исправляющая возможность применяемого кода зависит от числа избыточных битов, генерируемых кодером.
Если вносимая избыточность невелика, то существует опасность, что принимаемые данные будут содержать необнаруженное ошибки, которые могут привести к ошибкам в работе прикладного процесса. Если же использовать код с высокой исправляющей способностью (большой избыточностью), то это приводит к необоснованно низкой реальной скорости передачи данных.
В системах с обратной связью применяются процедуры обнаружения ошибок и переспроса, также называемые 'обнаружением ошибок с автоматическим запросом повторения" (ARQ, Automatic Repeat Request). В этом случае к данным добавляется контрольная информация, достаточная только для вычисления достоверности данных. Каждый принятый блок данных (кадр) должен быть подтвержден специальным кадром, посылаемым в обратном направлении. Такой кадр содержит уведомление о правильности данных или запрос на повторение кадра. Таким образом, достигается оптимальное сочетание: передаваемый код не содержит избыточных данных, а принимающая сторона может управлять коррекцией ошибок. При этом ARQ гакже может быть старт-стопного типа (SAW), с возвратом па N шагов (GBN) или селективного повторения (SR).
Для обнаружения ошибок в данных могут применяться несколько методов:
П посимвольный контроль четности (реализуется аппаратно в RS-232 с помощью контроля четности);
П поблочный контроль четности (например, в протоколах ARQ);
口 добавление избыточного контрольного кода (CRC);
П увеличение вероятности корректного распознавания начала посылки;
П естественные методы контроля данных.
Контроль четности
Порт RS-232 реализует аппаратный контроль четности (или как его еще называют, контроль паритета).. Пользователь может кон фи rypi 1 рова! ь порт на проверку четного или нечетного паритета или отсутствия контроля четности.
При включенном контроле четности к передаваемому байту добавляется еще один бит, дополняющий биты основного байта строго определенным образом. Например, пусть производится передача байта <1100 0101>. Общее количество единиц равно четырем. Если используется четный паритет, то бит паритета будет равен 0, и общее количество единиц в этом байте будет по-прежнему четным числом. Если же используется нечетный паритет, то бит паритета будет равен 1. Обшее количество единиц к этом случае, вместе с битом паритета, будет равно пяти, т. е. нечетному числу.
Принимающее устройство производит те же вычисления и сравнивает число единиц в полученном байте. При несовпадении генерируется ошибка четно-
сти. Однако, очевидно, что несовпадение четности произойдет только в случае искажения одного бита. Интересные эксперименты с контролем четности мы произведем в разд. 10.5.
При ошибке четности порт может генерировать аппаратное прерывание. Мы рассмотрим использование прерываний в гл. 8.
Контрольная сумма
Один из способов повышения достоверности получаемых данных заключается в дополнении посылки контрольной суммой. Контрольная сумма —— обычно один, два или четыре байта (в случае необходимости может быть и больше), полученные некоторым преобразованием данных посылки, подсчитываемые перед отправкой и включаемые в посылку. Принимающая программа производит аналогичную операцию над данными и сверяет вычисленную и полученную контрольные суммы. Если помеха изменила один или несколько байт посылки, вероятность совпадения контрольных сумм очень мала.
В этой главе мы кратко опишем наиболее распространенные методы вычисления контрольных сумм, а в г^. 75 займемся реализацией.
Простая контрольная сумма
Самая простая контрольная сумма может быть вычислена как исключающее ИЛИ всех информационных байт посылки.
Для вычисления исключающего ИЛИ в языке С (C++) используется оператор л. а в языке Pascal/Delphi — оператор xor.
Несмотря на примитивность такого метода, вероятность гого, что помеха исказит посылку таким образом, что наложение байт останется неизменным, достаточно мала. А очевидным преимуществом является простота реализации.
Контрольная сумма LRC
Контрольная сумма LRC (Longitudinal Redundancy Check) это один байт, который вычисляется передающим устройством и добавляется к концу сообщения. Принимающее устройство также вычисляет LRC в процессе приема и сравнивает вычисленную величину с полем контрольной суммы пришедшего сообщения. Если суммы не совпали, значит, имеет место ошибка.
LRC вычисляется сложением поеледовательности байтов сообщения, отбрасывая все переносы, и затем двойным дополнением результата, т. е. это 8битовое поле, где каждое новое прибавление символа, приводящее к результату более чем 255, вызывает простое перескакиванис через 0. Так как это поле не является 9-битовым, перенос отбрасывается автоматически.
Язык С не проверяет выход за границы типа. В Pascal (и, естественно, Delphi тоже) это сложнее, т. к. при переполнении вызывается исключение, и программа останавлИвается с ошибкой **Run-time error 201: Range check error*. Чтобы этого избежать, следует отключать контроль переполнения при вычислении или программно исключать возможность переполнения.
Таким образом, алгоритм генерации LRC выглядит так:
Сложить все байты сообщения, исключая стартовые и конечные символы, складывая их так, чтобы перенос отбрасывался.
Отнять получившееся значение от числа $FF — это является первым дополнением.
Прибавить к получившемуся значению 1 ——это второе дополнение.
Реализацией этого алгоритма мы займемся в 乙?. 15.
Контрольная сумма CRC16
Контрольная сумма CRC16 (Cyclical Redundancy Check) это 16-разрядная величина, т. е. два байта. CRC16 вычисляется передающим устройством и добавляется к сообщению. Принимающее устройство также вычисляет CRCI6 в процессе приема и сравнивает вычисленную величину с полем контрольной суммы пришедшего сообщения. Если суммы не совпали, значит, имеет место ошибка.
В 16-битовый регистр CRC предварительно загружается число $FFFF. Процесс начинается с добавления байтов сообщения к текущему содержимому регистра. Для генерации CRC используются только 8 бит данных. Старт- и стоп-биты, бит паритета, если он используется, не учитываются в CRC.
В процессе генерации CRC каждый 8-битовый символ складывается по исключающему ИЛИ (xor) с содержимым регистра. Результат сдвигается в направлении младшего бита, с заполнением 0 старшего бита. Младший бит извлекается и проверяется. Если младший бит равен I, то содержимое регистра складывается с определенной ранее фиксированной величиной по исключающему ИЛИ. Если младший бит равен 0, то исключающее ИЛИ не делается.
Этот процесс повторяется, пока не будет сделано восемь сдвигов. После последнего (восьмого) сдвига, следующий байт складывается с содержимым регистра, и процесс повторяется снова. Финальное содержание регистра после обработки всех байт сообщения и есть контрольная сумма CRC.
Таким образом, алгоритм генерации CRC выглядит так:
16-битовый регистр загружается числом $FFFF и используется далее как регистр CRC.
Первый байт сообщения складывается по исключающему ИЛИ с содержимым регистра CRC. Результат помещается в регистр CRC.
Регистр CRC сдвигается вправо (в направлении младшего бита) на один бит, старший бит заполняется 0.
(Если младший бит 0): Повторяется шаг 3 (сдвиг).
(Если младший бит 1): Делается операция исключающее ИЛИ регистра CRC и полиномиального числа $А001.
Шаги 3 и 4 повторяются восемь раз.
Повторяются шаги 2—5 для следующего сообщения. Это повторяется до тех пор: пока все байты сообщения не будут обработаны.
Финальное содержание регистра CRC и есть контрольная сумма.
Реализацией этого алгоритма мы займемся в гл. 15.
Стартовый байт
Для четкого распознавания начала посылки желательно, чтобы стартовый байт (или стартовая последовательнослъ) не встречался в самих данных. Для обеспечения уникальности стартового байта (или, точнее говоря, стартовой посылки) применяют один из способов:
программное исключение стартовых символов из данных;
аппаратное дублирование битов;
口 программное дублирование байт.
Первый способ заключается в простом анализе данных. Так, если данные состоят из цифр и букв, то логично в качестве стартовой посылки применить, например, символ ”$” или последовательность символов "!!!”. Появление такой последовательности внутри самих данных исключено. Однако такое исключение можно произвести только в строковых (например, ASCII) протоколах передачи.
Второй способ заключается в принудительном исключении символа начала посылки. Для синхронизации кадров используется комбинация типа "флаг” (двоичный кол <01111110>). Началом передаваемого блока данных (кадра) является первьп! байт после флага, отличный от него. После передачи последнего 14нформационного байта сразу передаются флаги. Для обеспечения прозрачности кода передаваемой информации используется процедура ибит- стаффингования": принудительное добавление нулевого бита после следующих подряд пяти единиц. Таким образом, появление шести единиц подряд (как в коде флага) исключено. Такие алгоритмы реализуются аппаратно, например, в протоколах HDLC (High-level Data Link Control, Высокоуровневый протокол управления каналом передачи данных).
И, наконец, программное обеспечение уникальности стартовой посылки производится на уровне байт или последовательности байт. Один из способов заключается в использовании двух байт и дублировании первого, если он попадается в данных. Например, стартовой последовательностью выбирается последовательность "$10, $01”. При этом оговаривается, что если "$10" встретится внутри данных (или контрольной сумме), то этот байт будет дублирован, т. е. будет передано "$10, $10”. Таким образом гарантируется, что стартовая поеледовательность будет уникальна, т. к. такая же последовательность в данных будет передана как ”$10, $10, $01".
Другой байтовый способ реализуется, например, в протоколе SLIP (Serial Line IP). В этом случае пакет данных (кадр) должен завершаться символом ”$С0” ("коней”). Во многих реализациях кадр и начинается с этого символа, что позволяет принимающей стороне эффективно отбросить все принятое до передачи кадра. Если какой-либо байт кадра равен символу "коней", то вместо него передается 2-байтовая последовательность "$DB, $DC". Если же байт пакета равен ”$DB”,то вместо него передается поеледовательность "$DB, $DD”.
2.5-4. Другие способы повышения достоверности данных
Кроме контрольной суммы, обеспечивающей, в общем-то, самый надежный способ контроля, можно применять и естественные способы. Например, если передаваемое число представляет собой значение кода АЦП, веса или давления, то логично проверять такие числа на попадание в естественный для них диапазон: код АЦП от 0 до 65535, вес от 0 до максимальной нагрузки, давление ——не меньше нуля и т. д.
Еще один способ заключается в увеличении активной паузы, т. е. после включения приемопередатчика в режим передачи следует выдержать паузу в 1,5—2 кадра. При активном передатчике помехе трудно будет "прорваться** к приемникам, а, следовательно, все абоненты зафиксируют тайм-аут приема, сбросят буферы и настроятся на прием новой посылки. Такой способ применяется, например, в протоколе MODBUS (см.乙】.4).
Глава 3
Последовательные протоколы 旧М PC
В этой главе будут описаны протоколы, имеющие непосредственное отношение к персональному компьютеру. Мы не будем затрагивать вопросы, связанные с аппаратной реализацией интерфейсов, а рассмотрим только лишь сами протоколы.
Мышь
Многие еще помнят, что в старых компьютерах манипулятор "мышь* подключался не к PS/2, а к обычному COM-порту. Впрочем, метод подключения ничего не меняет ——мышь была и остается устройством, использующим последовательный интерфейс RS-232.
Наиболее распространены два протокола обмена данными с мышью: Microsoft Mouse и PC Mouse. Первый протокол представляет собой группу форматов, основанную на Microsoft-протоколе. В настоящее время именно эта группа стала основной для координационных устройств, подключаемых к последовательному порту.
Базовый протокол Microsoft Mouse
В случае использования базового протокола Microsoft Mouse порт программируется на 1200 бит/с, 7 бит данных без проверки четности и 1 стоп-бит. Протокол обеспечивает передачу информации о двух кнопках. По каждому событию выдается информация из трех байт в весьма запутанном формате, показанном в листинге 3.1.
Листинг 3.1.ПротоколMicrosoft Mouse
1 байт:一1LBRBY7Y6Х7Х6
2 байт:-ОХ5Х4ХЗХ2XIХО
3 байт:-ОY5Y4Y3Y2Y1Y0
lb и rb передают состояние левой и правой кнопок соответственно (причем 1 означает "кнопка нажата"), а биты х, y — относительное перемещение мыши со времени последней посылки. Таким образом, первый байт отличается от остальных установленным в 1 шестым битом. Он служит для синхронизации начала посылки.
Положительное значение приращения по координате X соответствует перемещению указателя вправо, а по координате Y — вниз.
Протокол Microsoft Plus
Для обеспечения нормальной работы с трехк!юпочными устройствами протокол Microsoft Mouse пришлось дополнить четвертым байтом, который служит единственной цели ——обеспечению передачи состояния средней кнопки мыши (в пятом разряде, обозначенном символом "М"). Следует учитывать, что передача пакета из четырех байт выполняется только в случае изменения состояния средней кнопки, а в остальных случаях передаются первые три байта. Этот протокол получил название Microsoft Plus (листинг 3.2).
i Листинг 3.2» Протокол Microsoft Plus
1
байт:
-1
LB RB
Y7
Y6 Х7 Х6
2
байт:
一0
Х5 Х4
ХЗ
x2 XI X。
3
байт:
-0
Y5 Y4
Y3
Y2 Y1 Y0
4
байт:
-0
N 0
0
ООО
Протокол 3D Serial Mouse
Появление на рынке более сложных устройств, снабженных дополнительными возможностями, естественно, влечет за собой появление новых протоколов. Например, формат данных трехкоординатной мыши (3D Mouse) включает координату Z(ZO—Z3, целое число со знаком) и состояние специальной кнопки R0 (листинг 3.3).
Листинг 3.3, Протокол 3D Serial Mouse
134134байт:—:байт: — О байт: — О байт: — Обайт:—:байт: — О байт: — О байт: — ОLBХ5Y5ОLBХ5Y5ОRBХ4Y4R0RBХ4Y4R0Y7ХЗY3Z3Y7ХЗY3Z3Y6Х2Y2Z2Y6Х2Y2Z2Х7XIY1Z1Х7XIY1Z1Х6 хоY0Z0Х6 хоY0Z0
1
3
4
1
3
4
байт:—:
байт: — О байт: — О байт: — О
байт:—:
байт: — О байт: — О байт: — О
LB
Х5
Y5
О
LB
Х5
Y5
О
RB
Х4
Y4
R0
RB
Х4
Y4
R0
Y7
ХЗ
Y3
Z3
Y7
ХЗ
Y3
Z3
Y6
Х2
Y2
Z2
Y6
Х2
Y2
Z2
Х7
XI
Y1
Z1
Х7
XI
Y1
Z1
Х6 хо
Y0
Z0
Х6 хо
Y0
Z0
Протокол PC Mouse
Протокол PC Mouse обеспечивает передачу информации о трех кнопках и программирует порт на 1200 бит/с, 8 бит данных без контроля четности и 1 стоп-бит. Согласно этому протоколу, по каждому событию выдается информация из 5 байт (листинг 3.4).
Листинг 3.4. Протокол PC Mouse
1
байт:
1
0
0
0
0
LB
МВ
RB
2
байт:
Х7
Х6
Х5
Х4
ХЗ
Х2
XI
ХО
3
байт:
Y7
Y6
Y5
Y4
Y3
Y2
Y1
Y0
4
байт:
Х7
Х6
Х5
Х4
ХЗ
Х2
XI
ХО
5
байт:
Y7
Y6
Y5
Y4
Y3
Y2
Y1
Y0
Здесь lb, мь, ив ― состояния, соответственно, левой, средней и правой кнопок, причем 0 означает, что кнопка нажата. Дублирование байтов используется для идентификации первого байта посылки.
Совместимость протоколов
Стоит отметить, что протокол для мышей, подключаемых к порту PS/2, отличается (причем даже аппаратно!) от протокола COM-мышей. В манипуляторах имышь", работающих с обоими протоколами, просто реализованы два интерфейса одновременно. Подк,пючить мышь, работающую только с RS-232, к PS/2 с помощью обычного переходника не получится.
Модем
Как известно, данные в компьютере представлены в цифровой форме — закодированы в виде нулей и единиц, которым физически соответствует низкий или высокий уровень напряжения. Телефонная же сеть рассчитана на передачу речевых сообщений, представляемых в форме аналоговых электрических сигна.чов, поэтому непосредственная передача цифровой информации через телефонную сеть невозможна. Для п реобразова н и я форм пред-
ставления информации необходимо некоторое устройство, включаемое между компьютером и телефонной линией. Такое устройство называют модемом (сокращение от М Одул ятор-Д Е М одул я тор).
В нашей книге рассматриваются основные команды для работы с модемами и некоторые протоколы, используемые для передачи файлов. Всю аппаратную начинку мы оставим за кадром.
Основными функциями протоколов передачи файлов являются:
口 обеспечение безошибочной передачи данных;
口 управление потоком передаваемых данных;
口 передача вспомогательной информации;
口 защита соединения.
Для реализации этих функций данные разбиваются на кадры определенной длины, II в каждый кадр включается контрольная информация (см. гл. 2) для обнаружения ошибок. На приемной стороне производится аналогичное вычисление, и полученное значение контрольной информации сравнивается с принятым. При совпадении принимающая сторона посылает подтверждение правильного приема блока (АСК, AC Knowledge), а при несовпадении ——запрос на повторную передачу данного блока (NACK, No ACKnowledge, неподт- верждение).
Мы опишем наиболее распространенные протоколы передачи файлов. Все они рассчитаны на отсутствие аппаратной защиты от сбоев, и именно поэтому наиболее интересны для рассмотрения. Эта информация может оказаться полезной при составлении своего протокола обмена.
Команды управления модемом
Для управления модемом используются команды, представляющие собой поеледовательность определенных символов, воспринимаемых модемом как команды. Такие последовательности называются АТ-командами. ”АТ” (ATtention, внимание)——это префикс, который ставится перед одной или несколькими командами.
Команды можно набирать из окна терминала, предварительно установив соединение с портом, к которому подключен модем. Готовые терминальные программы можно или найти в Интернете, или воспользоваться стандартным терминалом, входящим в поставку Windows.
Команды могут быть набраны прописными или строчными буквами и должны содержать численные параметры, если этого требует формат команды. Если численный параметр пропущен, его значение по умолчанию принимается равным нулю. При успешном выполнении команды модем посылает сообщение "Ок”.
Существуют две команды, не требующие префикса “АТ". Это команда а/, означающая повтор предыдущей команды, и команда +++, называемая Escape-последовательностью. Escape-последовательность используется для переключения модема в командный режим из режима передачи данных.
Остальные команды являются АТ-командами и требуют префикса "АТ”. Многие команды требуют дополнительного префикса, следующего за префиксом АТ. В качестве таких префиксов выступают знаки '*&" (логическое И), "V (слеш), ^**' (знак умножения), ^%" (знак процента), '^" (решетка), "+" (знак плюс).
Набор АТ-ко манд конкретного модема может отличаться от набора команд других производителей, однако существует список команд, поддерживаемый всеми модемами. Список таких команд приводится в справочной части книги, в гл. 23.
Регистры модемов
Список регистров зависит от конкретной модели модема, однако, как и с АТ -командами, существует набор регистров, реализуемый во всех моделях модемов.
Регистр представляет собой ячейку в энергонезависимой памяти модема. Ячейки нумеруются с нуля. Запись в регистр производится с помощью АТ-команды ATSr = и. Например, для установки значения 3 в нулевой регистр нужно выполнить команду atso = 3. Чтение регистра производится с помощью АТ-команды ATSr?. Например, для получения содержимого регистра 7 НУЖНО ВЫПОЛНИТЬ Команду ATS7?.
Список основных регистров модема приводится в справочной части нашей книги, в ал. 22.
Протоколы передачи файлов
Конечно, чтобы рассмотреть все появившиеся на сегодняшний день протоколы, потребуется книга в несколько раз больше нашей. Мы же рассмотрим наиболее используемые и интересные с точки зрения реализации протоколы.
ASCII-протокол
Этот протокол работает без коррекции ошибок. В результате, при передаче файлов по телефонным каналам из-за шума принятый файл сильно отличается от передаваемого. Если вы передаете короткие текстовые сообщения, ошибки могут быть легко исправлены. Если же вы передаете выполняемый файл, то ошибки при передаче могут стать роковыми, а полученная программа не будет работать.
Протокол XModem
Протокол XModem, разработанный Бардом Христенсеном (Ward Christensen) в 1977 году, благодаря широкому использованию в справочных службах и введению в недорогие коммуникационные программы для PC стал фактическим стандартом для связи между персональными компьютерами.
Поеледовательность операций, выполняемых приемником и передатчиком, следующая. Передающий компьютер начинает передачу файла только после приема от принимающего компьютера знака NAK (Negative Ac Knowledge, неподтверждение или отрицательное подтверждение), пределаbjiяюиieго собой последовательность <0010101> в кодировке ASCII. Принимающий компьютер передает эту последовательность до тех пор, пока не начнется передача собственно фаНпа. Если переданы девять знаков NAK. а передача файла не началась, процесс должен быть возобновлен вручную.
riocjic приема знака NAK передающиii компьютер посылает знак начала блока SOH (Start Of Header, значение $()1), два номера блока (сам номер и его двоичное дополнение по Единицам"), блок данных из 128 байт и контрольную сумму блока CS (Check Sum). Блоки нумеруются ио модулю 256. Контрольная сумма размером в 1 байт представляет собой остаток от деления на 255 суммы значений кодов ASCII знаков, входящих в блок данных.
Принимающий компьютер тоже вычисляет контрольную сумму и сравнивает ее с принятой. Если сравниваемые значения различны, либо пронию 10 секунд, а прием блока не завершен, принимающий компьютер носьшает передатчику знак NAK, означающий запрос на повторную передачу последнего блока. Если блок принят правильно, приемник передает подтверждение его приема знаком АСК (кол $06). В случае если следующий блок не поступил в течение 10 секунд, то передача знака АСК повторяется до тех пор, пока блок не будет принят правильно. После девяти неудачных попьь ток передачи блока связь прерывается.
В протоколе используется двукратная передача номера. Это исключает повторную передачу одного и того же блока из-за потери полтвержлаюшего сообщения. Принимающий компьютер контролирует уникальность номеров принимаемых блоков. Если блок ошибочно передан повторно, то он сбрасывается. После успешной передачи всех данных передающий компьютер посылает знак завершения передачи EOT (End Of Transmission, код $04). сообщающий об окончании передачи файла.
Перерыв в передаче блока свыше одной секунды считается перерывом связи.
Преимущества данного протокола перед другими заключаются в его доступности для разработчиков программных средств, простоте реализации на языках высокого уровня, малом объеме приемного буфера (256 байт) и возможности передачи не только символьных (в кодах ASCH), но и бинарных файлов. Последнее возможно благодаря тому, что конец файла определяется подсчетом переданных байтов и использованием вместо знака файлового маркера (<Ctrl>+<Z>) специального сигнала завершения.
К основным недостаткам протокола XModeni можно отнести: низкую производительность, обусловленную в основном использованием механизма ARQ с подтверждением; большую вероятность необнаружеиных ошибок; необходимость задания имени файла при приеме и относительно большой объем передаваемой служебной информации.
Последующие модификации протоколу XModeni были направлены на устранение этих и некоторых других его недостатков.
Протокол XModem-CRC
Более защищенным от ошибок является протокол XModem-CRC (Cyclic Redundancy Check, циклический избыточностный контроль). Xmodem-CRC — протокол с проверкой циклическим избыточным кодом. В нем 8-битовая контрольная сумма заменена 16-битовым циклическим избыточным кодом. Этот протокол гарантирует вероятность обнаружения ошибок, равную 99,9984%, т. е. только один из 700 биллионов “плохих” пакетов будет иметь правильный CRC-код. Протокол XModem-CRC также передает данные пакетами по 128 байт.
Протокол XModem-IK
Протокол XModeni-1К представляет собой модификацию протокола XModem-CRC с блоками длиной до 1024 байт. Использование блоков длиной 1 Кбайт позволяет снизить задержки при передаче файлов по системам связи с временным уплотнением, с использованием современных модемов и в сетях с коммутацией пакетов, где длина пакета, как правило, равна величине 1024 байт либо кратна ей. Кроме того, по сравнению с обычным протоколом XModem, уменьшена относительная доля заголовков в общем объеме передаваемой информации.
Основной особенностью протокола XModem-1К является возможность изменения длины пакета. Если передача идет без ошибок, протокол XModeni-1К увеличивает размер пакета со 128 до 1024 байт. При увеличении числа ошибок размер пакета снова уменьшается. Такое изменение длины пакета позволяет увеличить скорость передачи файлов.
Для передачи приемнику сообщения об увеличении длины передаваемого блока вместо знака SOH (код $01) в начале блока ставится знак STX (Start of TeXt, начало текта, код $02).
Передатчик не должен изменять длину блока до тех пор, пока не будет принят знак АСК для текущего блока. Игнорирование этого ограничения может привести к тому, что ошибки не будут обнаружены.
Протокол YModem
Протокол YModem разработал Чак Форсберг (Chuck Forsberg) в 1984— 1985 годах. Протокол YModem похож на протокол XModem-IK, но имеет два отличия:
протокол YModem может передавать пли принимать за один заход несколько файлов;
口 вместе с файлом передаются все его атрибуты. В результате как минимум имя файла и дата остаются неизменными.
Имя файла (возможно, с указанием пути) перелается как строка кодов ASCII, завершаемая нуль-символом. Этот формат имени файла используется в функциях, ориентированных на операционные системы типа MS-DOS (например, в функции fopen библиотеки языки С). В имя файла не включаются пробелы. Имя диска источники (нанрммер, "А:”,“В:” и т. д.) не передается. Если передатчик не ио/ыерживаст передачу знаков в обоих регистрах, имя передается в строчном регистре. Если в имя файла включен каталог, его название должно ограничиваться знаками 7".
Передача длины файла и каждого последующего поля произвольна. Длина файла представляется в блоке как десятичная строка, обозначающая количество байт в файле. Если передаваемый файл увеличивается во время передачи, то параметр "Длина файла** должен иметь значение, соответствующее максимально ожидаемому размеру или не передаваться вовсе.
Дата модификации файла является необязательным параметром. Дата модификации представляется в виде восьмеричною числа, указывающего время последнего изменения файла, и измеряется в секундах по Гринвичу, начиная с 1 января 1970 года. Дата 0 обозначает, что дата модификации неизвестна, и должна быть оставлена дата приема файла. Этот формат используется для исключения неопределенности при передачах фгниюв между различными временными зонами.
Если блок имени файла принят с ошибке!!, необходим запрос на повторную передачу. Прием блока с именем файла, успешно открытого для записи, подтверждается знаком АСК. Если фа/1п не может быть открыт для записи, то приемник прерывает передачу с помощью знака CAN.
Далее приемник инициирует передачу содержимого файлов в соответствии с протоколом XModem-CRC. После того как содержимое файла передано, приемник запрашивает имя следующего файла.
Передача нулевого имени c])aiina може? означать, во-первых, что групповая передача завершена и, во-вторых, что запрошенные у передатчика файлы не могут быть открыты для чтения.
Протокол YModem-G
Вариант G протокола YModem обеспечивас! высокую эффективность передачи данных. Он используется приемником, которьН彳 инициирует групповую передачу путем посылки знака ”g” вместо "с” Передатчик, распознавший этот знак, прекращает ожидание обычных подтвержден и й по каждому переданному блоку и передает поеледователы 1 ые блоки на полной скорости с использованием метода управления потоком, такого как XON/XOFF.
Прежде чем передавать файл, передатчик ожидаег поступления послсмова- тельности знаков ”g”,а в конце передачи каждого файла — подтверждающею знака АСК. Такой способ синхронизации позволяе'1 приемнику открывать и закрывать фаг^лы в нужное время.
Протокол YModcm-G предназначен для использования с модемами, автоматически осуществляющими коррекцию ошибок на аппаратном уровне, п, соответственно, в нем упрошена защити от ошибок, г. к. сс кыполняст сам модем. Не следует использовать этот протокол, если ваш модем не поддерживает аппаратной коррекции ошибок.
Протокол ZModem
Протокол ZModem для общего применения был разработан в 19x6 году компанией Telnet и введен в большинство коммуникационных программ, В настоящее время этот протокол получил наиболее широкое распространение.
Представляя собой развитие протоколов XModem и YModem, ZModem устраняет их недостатки и, при соблюдении совместимости, имеет ряд преимуществ:
口 высокое бысгродеiiствие;
□ динамическая адаптация к качеству канала связи посредством изменения в широких пределах размера передаваемых блоков;
П возможность возобновления прерванной передачи файла с того места, на котором произошел сбой;
повышенная достоверность передачи благодаря нс пол ьзованпю 32-раз- рялно!! проверочной контрольной суммы (CRC32);
口 возможность отключения функции контроля ошибок передаваемых блоков при исиользованпи модемов с аппаратной коррекцией ошибок.
Протокол ZModeni, так же как н протокол XModem-1 К, может изменять длину пакета (блока) от 64 до 1024 6а"т в зависимости от качества линии.
Кроме того, протокол обладает следующей полезной особенностью: если при передаче файла произошел сбой на линии, а вы не успели передать весь файл, то в следующий раз при передаче этого же файла он автоматически начнет передаваться с того же места, где произошел обрыв связи. Таким образом, очень большие файлы вы можете передавать ио частям.
Протокол BiModem
Особенностью протокола Bi Modem является возможность одновременной передачи двух файлов в разных направлениях. Кроме того, одновременно с передачей файлов вы можете побеседовать с оператором удаленного компьютера при помоши клавиатуры.
Протокол Kermit
Этот протокол был разработан в КолумбнПском университете в 1981 году для связи между различными типами компьютеров, включая большие компьютеры. м и н и - комп ьютеры и персональные компьютеры. В отличие от протоколов XModem и Zmodem, он использует для передачи данных пакеты переменной длины и максимальным размером 94 байт.
Так же как и YModem, протокол Kermit может передавать или принимать несколько файлов за один сеанс, и, в дополнение, Kermit использует пред- юрительиую компрессию данных для увеличения эффективной скорости обмена данными.
Глава 4
Промышленные
последовательные протоколы
В этой главе будут кратко описаны современные промышленные протоколы п оследо вате л ьно й связи. Эта информация будет интересна не только программистам, занимающимся промышленной автоматизацией, но и всем тем, кто занимается построением собственных протоколов обмена. Мы не будем подробно рассматривать аппаратные реализации интерфейсов, а акцентируем внимание на принципах построения протоколов.
Протокол MODBUS
Протокол MODBUS является международным стандартом, который поддерживают многие фирмы — производители контроллеров технологического оборудования. Протокол предполагает одно активное (запрашивающее) устройство в линии (мастер) и позволяет опрашивать устройства、обращаясь к ним по уникальному адресу. Синтаксис команд протокола позволяет адресовать 254 устройства, соединенные в линию. Обычно подразумевается, что физический уровень линии должен соответствовать стандарту RS-422 (четырехпроводный, дуплексный) или RS-485 (двухпровод! 1ый, полудуплексный, с захватом линии), однако при соединении 'точка-точка*' тот же формат команд может быть использован на любом иоследовательном асинхронном физическом интерфейсе, в том числе RS-232.
Контроллеры соединяются, используя технологию 'Улавный-подчиненный", при которой только одно устройство (master, главный) может инициировать передачу (сделать запрос). Другие устройства (slaves, подчиненные) передают запрашиваемые главным устройством данные или производят запрашиваемые действия. Типичное главное устройство включает в себя ведущий (host) процессор и панели программирования. Типичное подчиненное уст-
ройство — программируемый контроллер. Контроллеры могут быть соединены напрямую или через модем.
Главное устройство может адресоваться к индивидуальному подчиненному или может инициировать широкую передачу сообщения иа все подчиненные устройства. Подчиненное устройство возвращает сообщение в ответ на запрос, адресуемый именно ему. Отвепы не возвращаются при широковеша- тельном запросе от главного.
Форматы запроса и ответа имеют вил:
口 адрес устройства;
код функции;
8-битные байты данных;
口 контрольная сумма.
Код функции в запросе говорит подчинен ному устройству, какое действие необходимо произвести. Байты данных содержат информацию, необходимую для выполнения запрошенной функции. Например, код функции 3 подразумевает запрос на чтение содержимого регистров подчиненного. Если подчиненный дает нормальный ответ, код функции в ответе повторяет код функции в запросе, и в байтах данных содержится затребованная информация. Если имеет место ошибка, то код функции модифицируется, и в байтах данных передается причина ошибки. В сетях MODBUS может быть использован один из двух способов передачи: ASCII или RTU (Remote Terminal Units, удаленные терминальные устройства). Пользователь выбирает необходимый режим вместе с другими параметрами (скорость передачи, режим паритета и т. д.) во время ко н фи гур и ро на н 11 я каждого контроллера.
Протокол MODBUS-ASCII
При использовании ASCII-режима каждый байт сообшения передается как два ASCII-символа, а именно, две шестнадцатеричные цифры (символы О— 9, A—F). Каждое сообщение передается непрерывным потоком. Главное преимущество этого способа заключается в том, что время между передачей символов может быть до 1 секунды, без возникновения ошибок при передаче, Байты могут передаваться как с контролем четности, так н без него. В первом случае используется один стоп-бит, а во втором ——два стоп-бита. Формат байта протокола MODBUS-ASCII показан в табл. 4.1.
Таблица 4.1“ Формат байта протокола MODBUS-ASCII
С контролем четности
Без контроля четности
1 старт-бит
7 бит данных, младшим битом вперед
1 бит паритета
1 старт-бит
7 бит данных, младшим битом вперед Нет бита паритета
Таблица 4.7 (окончание)
С контролем четности
Без контроля четности
1 стоп-бит
2 стоп-бита
Контрольная сумма (LCR)
Контрольная сумма (LCR)
В ASCII-режиме сообщение нами пае юя с двоеточия (символ ":”,код S3A) и заканчивается п ос ледовател ьностыо 〈возврат каретки >, < перевод строки> (CRLF, код SOD, $0А). Так как сообщен не состоит из шестнад на гери ч ных цифр, то допустимым набором символов является 0—9 и A—F.
Принимающее устройство постоянно ожидает символа ”:”. После того как он принят, устройство принимает все следующие символы согласно (формату сообщений (табл. 4.2). Интервала между символами сообщения могут быть не более 1 секунды. Интервал более секунды принимающее устройство распознает как ошибку.
Таблица 4.2. Формат посылки протокола MODBUS-ASCII
Начало
Адрес
Номер
Данные
LCRКонец
посылки
абонента
функции
посылки
1 символ (знак ":*')
2 символа
2 символа
N символов
2 символа 2 символа (CRLF)
Протокол MODBUS-RTU
При использовании RTU-протокола каждый байт сообшения содержит две 4-битовые шестнадцатеричные цифры. Основное преимущество этого протокола в том, что с его помошыо можно передавать значительно больший объем информации, чем за то же время с помощью ASCII-протокола. Каждое сообщение передается как непрерывны!! поток.
Протокол может либо использовать контроль чстности (в этом случае используется один стоп-бит), либо работать без контроля четности (в этом случае используются два стоп-бита). Формаг баНта протокола MODBUS-RTU показан в табл. 4.3.
Таблица 4.3. Формат байта протокола MODBUS-RTU
С контролем четностиБез контроля четности
старт-бит1 старт-бит
8 бит данных, младшим битом вперед 8 бит данных, младшим битом вперед
бит паритетаНет бита паритета
Таблица 4.3 (окончание)
С контролем четностиБез контроля четности
1 стоп-бит2 стоп-бита
Контрольная сумма (CRC)Контрольная сумма (CRC)
В RTU-режиме сообщение начинается с интервала тишины, рашюго времени передачи 3.5 символов при данной скорости передачи в сети. Это позволяет принимающему устройству абсолютно точно понять, что это не перерыв в передаче предыдущего сообщения. Такая информация необходима приемнику, чтобы сообщение, принятое не с начала (например, при включении приемника), не было интерпретировано как начало посылки. Другими словами, это гарантирует, что первый нолученньп! после тишины символ будет первым символом сообщения, а, кроме того, если получено сообщение оговоренной в протоколе длины, то это достоверно будет полное сообщение.
Первым полем после тишины передастся адрес устройства, которому адресуется сообщение, затем следуют номер фупкции, данные и контро刀ьная сумма. Вслед за последним передаваемым символом также следует интервал тишины продолжительностью нс менее 3,5 символов. Новое сообщение может начинаться после этого интервала (табл. 4.4).
Таблица 4.4. Формат посылки протокола MODBUS RTU
Начало посылки
Адрес абонента
Номер функции
Данные
LCR
Конец посылки
3,5 символа тишины
8 бит
8 бит
N байт
16 бит
3,5 символа тишины
Фрейм сообщения передастся непрерывно. Если интервал тишины продолжительностью 3,5 символа возник во время передачи фрейма, принимающее устройство завершает прием сообщения, сбрасывает принимаюпшй буфер н следующий байт будет воспринят как начало следующего сообшсния.
Таким образом, если новое сообщение начнется раньше 3,5-символьного интервалг1, принимающее устройство воспримет его как продолжение предыдущего сообщения. В этом случае будет установлена ошибка из-за несовпадения контрольных сумм.
Поля MODBUS протокола
Поле "Адрес абонента"
Адресное поле содержит два шест11а〃цатср“чнь1х символа (в ASCII-протоколе) или 8 бит двоичных символов (в RTU-протоколе). Допустимый ад-
рес передачи находится в диапазоне от 0 до 247. Каждому подчиненному устройству присваивается адрес в пределах от 1 до 247.
Адрес 0 используется для ш и роко ве i цате л ьи ой передачи, его распознает каждое устройство. Когда MODBUS- прото кол используется на более высоком уровне сети, широковещательная передача может не поддерживаться или может быть реализована другими методами.
Поле иКод функции"
Поле функции содержит два шести адцатерич н ых символа (в ASCII-протоколе) или 8 бит двоичных символов (в RTU-протоколе). Диапазон чисел от 1 до 255. Реализация всех функций не обязательна, контроллер может обрабатывать некоторое подмножество функций. Когда главный передает данные подчиненному, он указывает номер функции, которую должен выполнить подчиненный. В случае нормального ответа подчиненный повторяет оригинсыьный код функции Если имеет место ошибка, главному возвращается код функции с установленным в I старшим битом, а поле данных содержит описание или причину ошибки.
Например, главный передает подчиненному указание "прочитать группу регистров1'. Эта функция имеет код <0000 0011>, т. е. $03. Если подчиненный выполнил затребованное действие без ошибки, он возвращает такой же код. Если имеет место ошибка, то он возвращает < 1 000 0011>, т. е. $83.
Поле иДанные11
Поле данных в сообщении от главного к подчипеппому содержит дополнительную информацию, которая необходима полчипенному для выполнения указанной функции. Оно может содержать адреса регистров или выходов, их количество, счетчик передаваемых байтов данных и г. п.
Например, если главный запрашивает у подчиненного прочитать группу регистров (код функции 0x03), поле данных содержит адрес начгшьного регистра и количество регистров. Если главный хочет записать группу регистров (код функции 0x10), поле данных содержит адрес начального регистра, количество регистров, счетчик количества байтов данных и данные для записи в регистры.
Поле данных может отсутствовать (иметь нулевую длину) в определенных типах сообщений.
Поле "Контрольная сумма”
В MODBUS-еетчх используются лва метода контроля ошибок передачи. Содержание поля контрольной суммы завнент от выбранного способа передачи.
Когда используется ASCII-режим, поле контрольной суммы содержит два ASCII-символа. Конгрольная сумма является результатом вычисления LRC
@/. разд. ''Контрольная сумма LRC") над содержанием сообщения начиная с ":"и заканчивая CRLF (исключая сами символы начала и конца посылки).
Когда используется RTU-режим. поле контрольной суммы содержит 16-битовую величину. Контрольная сумма является результатом вычисления CRC (см. разд. “Контрольная сумма CRC /6) над содержанием сообщения. CRC добавляется к сообщению последним полем младшим байтом вперед.
Протокол CAN
Интерфейс CAN (Controller Area Network, локальная сеть контроллеров) предназначен для организации высоконадежных и недорогих каналов связи в распределенных системах управления. Он позволяет строить как дешевые мультиплексные каналы, так и высокоскороспiые сети.
В настоящее время С A N - и нтерфейс широко применяемся во многих областях, в том числе в промышленной автоматике и в аэро косм и чес ком приборостроении. С A N - и нтерфейс (или CAN-Bus) чаще всего используется как связующее звено между главной магистралью управляющего устройства и множеством вспомогательных датчиков, механизмов и т. п., подключение которых к центральной магистрали не всегда целесообразно.
Протокол CAN, разработанный фирмой Bosch, изначально проектировался для нужд автомобильной промышленности: такие компании как Chrysler, Ford, GM, Volvo, Audi, BMW, Renault, SAAB, Volkswagen, Toyota используют CAN для контроля и управления системами автомобилей, в том числе системами безопасности ABS и ASC. Разработанный интерфейс оказался настолько удачным, что к настояшему моменту он применяется повсеместно.
Типичными примерами применения CAN-сети являются системы контроля и управления текстильным оборудованием, производимого компаниями Cezoma, Lindauer Dornier, Rieter, Schlafliorst, Sulzer; пол и графи чес ки м оборудованием Heidelberger, Ferag; упаковочными машинами EDF, Island Beverages, Noilhrup King, Soudronic, TetraPak, Wepamat; деревообрабатывающим оборудованием компании Homag; производством полупроводников —Applied Materials.
Среди других важных областей применения сетм нужно отметить промышленную автоматизацию и системы управление роботами. ABB, Bosch, EngeL Kuka и другие компании уже применяют CAN в своем оборудовании. Edeka использует CAN в своем центре логистики Нс обходятся без применения CAN-технологий системы автоматизации и жизнеобеспечения зданий, где они используются в управлении кондиционированием (Colt International), поддержания температуры (Buderus), интегрированного контроля состояния помещений и освещения. Часто CAN применяют в таких системах жизнеобеспечения зданий, как лестничный контроль Saarland Hall in Saarbruecken и Suedwestfimk in Baden-Baden, управление лифтами и подъемниками Kone,
Orenstein & Koppel, Otis. Telelift, пожарный контроль. Основные производители медицинского оборудовапия Combat Diagnostics, Dr.ger, Fiesenius, GE Medical Systems, Philips Medical Systems, Siemens, Storz Endoskope, Toshiba также используют CAN в своих изделиях.
Характеристики протокола CAN
Интерфейс CAN имеет следующие основные характеристики.
口 Среда передачи данных в CAN-спсиификации не определена. Интерфейс с применением протокола CAN легко адаптируется к физической среде передачи информации. Это может быть ди ффере н и и lui ьн ы й сигнал, передаваемый по скрученной паре, оптоволокно, просто открытый коллектор и т. п.
П CAN -контроллер обеспечивает безотказную работу по стандарту ISO 11898 даже в условиях возникновения н и жепереч исле н н ых ситуациий:
любой из 3 проводов в шине оборван;
любой провод замкнут на питание;
любой провод замкнут на общий провод.
口 Скорость передачи задается программно и может достигать I Мбит/с. Пользователь выбирает скорость, исходя из расстояний, числа абонентов н емкости линий передачи. Максимальное число абонентов, подключенных к данному интерфейсу, фактически определяется нагрузочно!! способностью примененных приемопередатчиков.
口 Максимальная скорость передачи: I Мбит/с при ллине линии ло 40 м или 40 Кбмт/с при длине линии 1000 м. При этом практически любой CAN -кое ироллер допускает програм м ироваи ие скорости обмена от 1 Мбит/с до 10 Кбит/с.
口 Арбитраж организован так, что он нс увеличивает время реакции системы на более приоритетные сообщения.
口 Общее количество CAN-узлов не ограничено протоколом.
口 Сообщения по CAN-шине могут передаваться одному или одновременно нескольким узлам, настроенным на прием одних и тех же параметров.
Адресная информация (номер параметра) содержится в сообщении п совмещена с его приоритетом.
КоличестЕЮ байтов данных настраиваегся от 0 до 8.
口 Если хоть один узел в сети принял сообщение с ошибкой, это сообщение признается ошибочным для всех узлов сети.
Отказавшие узлы динамически отключаются от шины.
П Подавление синфазных помех осуществляется дифференциа,чыiым приемопередатчиком.
Обмен данными в протоколе CAN
CAN ——последователиная шина с поддержкой одновременной работы многих мастер-устройств, чю означает возможность для всех узлов CAN-сети пере дава г ь да иные и запрашивать шину нескольким узлам одновременно.
Устройства в CAN-системс соединяются по шинеч состоя шей из трех проводов (2 сигнальных и один общий).
Сообщения данных, передаваемые из любого узла по СА№【иине, могут содержать от 1 до 8 байт. Каждое сообщение помечено илеiiniфикатором, который в сети является уникальным (например, "Нагрев до 240'*, “Отказ на- грева", 'Ъункер загружен” и т. д.). При передаче другие узлы сети получают сообщение, и каждый из них проверяет идентификатор. Если сообщение имеет отношение к данному узлу, то оно обрабатывается, в противном случае —— игнорируется. CAN-контроллер каждого из устройств может обрабатывать одновременно несколько и;iei{ги(])пкатеров (например, контроллеры Siemens и Intel могут обрабатывать до 15 илеитл(|)икаторов). Таким образом, в каждом из устройств можно легко организовать несколько виртуальных каналов обмена ипформацие!! с различными устройствами, включая каналы одновременного получения сообщен и ii.
Идентификатор определяет тип н приоритет сообщения. Более низкому числовому значению идентификатора соответствует более высокое значение приоритета. Сообщение, имеющее более высокий приоритет, перелается раньше сообщения, имеющего более низкий приоритет. После сообщения с высоким приоритетом передне гея сообшснне с более низким приоритетом, если во время передачи не появится сообщение с более высоким приоритетом; затем передается сообщение с еше более низким приоритетом и т. д.
Обнаружение ошибок в протоколе CAN
CAN содержит 5-ступеичатып механизм обнаружения ошибок.
口 Циклический контроль по избыточности (CRC). Каждое переданное сообщение содержит контрольный код CRC, вычисленный передатчиком на основе содержания передаваемого сообщения. Приемные узлы выполняют аналогичную операцию, помечают обнаруженные ошибки и устанавливают соогветствуюшпе флаи.
口 Контроль передаваемого поля битов. В составе CAN-cooGuichiih псрела- ютс51 предопределенные битовые комбинации, которые копгролнруются при приеме. Если приемник обнаруживает иедопустимыН бит в одной из этих комби наци il то устанавливается флаг '*Ошибка формата'1,
口 Контроль сигнала ''Подтверждение нрнема”. Каждое переданное сообщение подтверждается приемником, а если этого не произошло, уста на вл и- вается флаг "Ошибка подгвержления прпема".
□ Текущий контроль логического уровня битов. Любой передатчик автоматически контролирует и сравнивает фактический логический уровень битов на шине с уровнем, который он передает. Если уровни не совпадают, помечается ошибка логического уровня битов.
П Контроль заполнения битов. CAN использует методику добавления заполняющего бита дпя дополнительного контроля передаваемых сообщений. После передачи пяти поеледовательных битов с одинаковым уровнем передатчик автоматически вводит в разрядный поток бит противоположной полярности. Приемники сообщения автоматически удаляют такие биты перед обработкой сообщения. Если обнаруживается шестом бит одинаковой полярности, то помечается ошибка заполнения битов.
В случае если обнаружена ошибка, узел, обнаруживший ошибку, прерывает передачу посылкой флага ошибки. При этом передатчик автоматически производит повторную передачу сообщения, что предохраняет все узлы от возникновения ошибок и гарантирует непротиворечивость данных в сети.
Протокол Profibus
Сеть Profibus ——это не зависящий от производителя открытый стандарт полевой шины для широкого ранга приложений в производственной автоматизации. Сеть Profibus — это комплексное понятие, она основывается на нескольких стандартах и протоколах. Эта сегь построена в соответствии с многоуровневой моделью ISO 7498-OSI i【состоит из трех уровней:
1 — физический уровень;
2 — канальный уровень;
7 — уровень прюгожений.
Открытость и независимость от производителя гарантируется стандартом EN 50 170, все остальное реализовано в соответствии со стандартом DIN 19245 (техника передачи данных, методы доступа, протоколы передачи, сервисные интерфейсы для уровня приложений, спецификация протоколов, кодирование, ком муни каци он f i ая модель и т. д.). С помощью Profibus устройства разных производителей могут работать друг с другом без каких-либо специальных интерфейсов. Семейство Profibus состоит из трех совместимых друг с другом версий: это Profibus-РА, Piofibus-DP и Profibus-FMS.
Физически Profibus ——это электрическая сеть, в основе своей использующая экранированную витую пару, или оптическая сеть на основе оптоволоконного кабеля. Скорость передачи по ней может варьироваться от 9,6 Кбит/с до 12 Мбпт/с. Наиболее часто используется витая пара с интерфейсом RS-485.
Для всех версий Profibus существует единый протокол доступа к шине. Этот протокол реализуется на втором уровне модели ISO (который называется FDL). Данный протокол реализует процедуру доступа с помощью маркера (token). Сеть Profibus состоит из ведущих (master) и ведомых (slave) станций. Ведущая станция может контролировать шину, т. е. может передавать сообщения (без удаленных запросов), когда она имеет право на это (т. е. когда у нее есть маркер). Ведомая станция может лишь распознавать полученные сообщения или передавать данные после соответствующего запроса. Маркер вращается в логическом кольце, состоящем из ведущих устройств.
Если сеть состоит только из одного мастера, то маркер не передается (в данном случае в чистом виде будет система ^'мacfep-пoдчинeнный,,). Сеть с минимальной конфигурацией может состоять либо из двух мастеров, либо из одного мастера и одного ведомого устройства.
Глава 5
СОМ-порты и Plug and Play
В это11 главе мы обсушим вопросы, связанные с автоматическим обнаружением устройств, иодключенЕплх к последовательным портам.
Кратко о Plug and Play
Появление огромного числа моделей 11ериферийных устройств привело к очевидной невозможности их ручного ко i к])и гури ро ван и я. Выбор портов, номеров аппаратных прерываний, ячеек памяти... А ведь все параметры надо выставить корректно, исключить конфликты оборудования. При этом следует учитывать, что некоторые устройства умеют мподвинутьсям на другие номера прерываний, а некоторые требуют фиксированного номера. Иными словами, очевидна необходимость автомати чес кого конфи гури рован и я устройств.
Протокол Plug and Play (дословно, "подключил и играй") позволяет достаточно просто подключать новое оборудование. Перед началом работы система (BIOS при начально" загрузке, Windows при запуске) опрашивает устройства, узнает их требования к системным ресурсам и пытается бесконфликтно разделить ресурсы между устройствами. Если это не удается, конфликтующие устройства будут работать некорректно. В этом случае необходимо вручную внести корректировки в настройки. Многие устройства, например, не поддерживают самостоятельное переключение диапазонов используемых ресурсов, но могут настраиваться с помощью специгьтьиых переключателей. Спецификация Plug and Play предусматрпвает также "горячее" подключение устройств, т. е. подключение во время работы.
Итак, основными функциями системы РпР (это сокращенное обозначение Plug and Play) являются:
определение подключаемых устройств:
口 идентификация устройств и уведомление операционной системы (ОС) об их появлении;
определение отключения устройства и уведомление об этом ОС:
автоматическое конфигурирование устройств без вмешательства пользователя.
Разумеется, в нашей книге мы рассмотрим только РпР-иротокол для последовательных портов и связанных с ними устройств. Более того, мы будем рассматривать исключительно практический аспект спецификации.
Запуск процедуры РпР
При старте Windows производит краткий опрос наличия устройств. При необходимости сушествует возможность запустить этот процесс вручную.
Например, в Windows 98 необходимо нажать кнопку Пуск, выбрать пункт Настройка, в открывшемся окне выбрать Панель управления и активизировать иконку Установка оборудования. После этого будет показано диалоговое окно, похожее па рис. 5.1 (вид диалогового окна может немного меняться в зависимости от версии Windows). Нажатие на кнопку Далее (точнее говоря, надо два раза нажать Далее, ответив утвердительно на предупреждаю щи й диалог) запускает процедуру быстрого поиска новых устройств. Те же действия можно выполнить, щелкнув правой кнопкой мыши по иконке Мой компьютер.
Рис. 5.1. Диалоговое окно поиска нового оборудования (Windows 98)
В Windows 2000/ХР активизировать процедуру PnP-опроса можно и из окна Device Manager (Менеджер устройств), нажав кнопку Обнаружение изменений аппаратуры (Scan fbr hardware changes) (рис. 5.2).
乌 Device Manager乌 Device Manager
乌 Device Manager
乌 Device Manager
♦團—mputei
岳 口 Disk drives
图夏 Display adapters
困 为 DVD/CD-ROM drives
困二弓 Floppy disk controllers
恒.t±J Floppy disk drives
ffi-^ IDE ATA/ATAPI controllers
困遽 Keyboards
口 E) Mice and other pointing devices f〉PS/2 Compatible Mouse
•+■粵 Monitors
:国野 Netwoik adapters
田 У Ports (COM & LPT)
困 @ Sound, video and game contiolleis
.—J Storage volumes
l+l 羽 System devices
用 奇 Universal Serial Bus cantralleis
Рис. 5.2. Окно менеджера устройств (Windows 2000)Рис. 5.2. Окно менеджера устройств (Windows 2000)
Рис. 5.2. Окно менеджера устройств (Windows 2000)
Рис. 5.2. Окно менеджера устройств (Windows 2000)
Если устройство не находится автоматически...
В Windows 98 SE существует проблема с обнаружением новых устройств, подключенных к COM-порту. Подключенные устройства могут не находиться даже после выполнения полной процедуры поиска.
Для обхода этой проблемы Microsoft рекомендует выключить АРМ (Advanced Power Management) для поеледовательных портов. Для этого необходимо внести следующее исправление в реестр Windows: значение ключа HB:EY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\VCOMM Enable Powe rManagement заменить С <0100 0000> на <0000 0000>.
Важно!
Некорректное внесение изменений в реестр может повлечь серьезные проблемы, вплоть до полного выхода системы из строя.
В худшем случае, если это не помогает, можно удалить все СОМ-порты из системы и, перезагрузив компьютер, заставить Windows найти их заново.
Где хранится информация о найденных устройствах
Структура реестра Windows 98
Последовательные, параллельные порты или модемы могут устанавливаться тремя способами:
□ установкой во время инсталляции Windows;
口 сервисом Plug and Play;
口 установкой вручную с помощью Помощника установки оборудования (Hardware Installation Wizard).
Каждое из установленных устройств имеет два раздельных ключа в реестре: ключ описания аппаратной части и ключ описания программной части (драйвер).
Аппаратный ключ реестра для последовательных портов расположен в ветке hkey_local_machIve\Enum вместе со всеми другими устройствами, обнаруженными в системе. Для устройств, не поддерживающих стандарт РпР, ключ располагается в ветке Enum\Root, а для устройств, совместимых со стандартом РпР, ключ будет располагаться в ветке Enum\SERENUM (для полноты описания отметим, что ключей в ветке Enum немного — для остальных устройств существуют ключи Enum\isA, Enuw\pcmcia и EnumxPC ). Примеры ключей приведены в табл. 5.1.
Таблица 5.九 Аппаратная часть описания устройств в реестре Windows 98
Ветка реестра
Описание
Enum\Root\*PNP0500\0000
Первый COM-порт, найденный системой, обычно СОМ1. Последующие найденные порты будут нумероваться \*PNP0500\0001 и т. д.
Enum\Root\*PNP0400\0000
Первый LPT-порт, найденный системой. Обычно LPT1
Enum\Root \ Pc г t s \ 0 0 0 0
Вручную установленный порт. Или СОМ или LPT
Enum\SERENUM\PNP0F0A\
PnP-последовательная Microsoft мышь, найденная
ROOT&*PNP0500&0000
на порту СОМ1 с помощью SERE NUM VXD
Таблица 5. / (окончание)
Ветка реестраОписание
Enum\LPTENUM\ABCl234 \РпР-параллельный порт с PnP-устройством, имею-
ROOT&*PNP04 00&0000щим идентификатор АВС1234, найденное с по
мощью LPTENUM.VXD
Аппаратные ключи реестра имеют несколько веток и значений. Большинство из них создаются системой РпР для внутреннего использования, но они доступны для всех приложений. Послсдовательные устройства сохраняют два значения.
FriendiyName ——понятное для пользователя системы название устройства. Например: мпоследовательный порт СОМ Г' или *'56К Внешний модем'*. Кпюч создается или согласно содержимому INF-файла или самим VCOMM.
PortName — короткое имя порта. Например, *'СОМГ' или "LPTl". Имена присваиваются системой. По этим именам производится открытие портов с помощью вызова createFiie. В Windows 2000/ХР имена портов можно сменить из окна Менеджера устройств (Device Manager) (см. разд. 13.4).
Программная часть ключа (ключ драйвера устройства) сохраняется в ветке
HKEY__LOCAL_MACHINE\System\CurrentControlSet\Services\Class
Для каждого порта системы создастся ключ в ветке \ports, например, \ports\0000. Для каждого модема ключ создается в ветке \modem, например, \modem\0000. Ссылка на ключ драйвера сохраняется также в аппаратной части ключа реестра, в имени "Driver".
В ключе драйвера создаются следующие значения.
ConfigDiaiog —— имя динамической библиотеки с функциями CommConfigDialog, GetDefaultCommConfig H SetDefaultCommConfig. Для COM-портов ЭТО обычно "serialui.dll*', ДЛЯ Модемов "modemui.dll”, ДЛЯ LPT портов этот ключ нс создается.
Contention — определяет статическую VxD. Для СОМ-нортов и PCMCIA-модемов зто обычно "^ved", а для LPT-портов "*vpdf,. Для нестандартных портов может быть установлено свое имя.
dcb — бинарное содержание структуры DCB (Device Cornrol Block. СМ. разд. 20.5). создаваемой ВЫЗОВОМ SetDefaultCommConfig. Это значе-
ние будет установлено при нажатии кнопки Установить значения по умолчанию В диалоговом окне CommConfigDialog.
口 DevLoader — ВССГДа vcomm”.
口 DriverDesc ——читаемое название устройства. Например: пПоследовательный порт”,"Порт принтера'*.
口 Enumerator ——модуль, отвечающий за обнаружение новых устройств данного типа. Для СОМ-портов это Hserenum. vxd'*, для LPT-портов это Mlptenum. vxd". Для нестандартных устройств может быть указан свой модуль.
口 EnumPropPages — имя DLL-модуля и имя функции (точка входа), вызывающая создание стран и цы свойств, для отображения свойств устройства в Менеджере устройств (Device Manager). Для COM-портов это "serialui. dll, EnumPropPages*', ДЛЯ МОДСМОВ ЭТО ^modemui .dll, EnumPropPages*', а LPT-порты обычно не имеют страницы свойств.
infPath — имя соответствующего 1 NF-файл а. Для обычных портов “MSPORTS •工NF”.
口 infSection ——имя секции в файле infPath. Для COM-портов обычно "ComPort'*, а ДЛЯ LPT-ПОрТОВ — “LptP。工t".
FriendlyDriver — имя драйвера, загружаемого при открытии порта по имени, определенному в аппаратной части ключа реестра. Если этот ключ не определен, будет загружен драйвер из ключа PortDriver. Для модемов ЭТОТ ЮПОЧ всегда Hunimodem. vxd'*.
PortDriver — имя драйвера, загружаемого при открытии порта по имени, определенному в аппаратной части кл юча реестра. Для СОМ-портов это "serial.vxd'\ а для LPT-портов это ”上pt.vxd”. Для внутренних и внешних модемов это поле не используется, однако для UART-совмсстимых PCIMCIA-модемов это значение устанавливается в "serial, vx^*.
口 PortSubciass — класс устройства:
00 для параллельных портов;
01 для п осл едо вате л ьн ы х портов;
02 для модемов.
5.4.2. Структура реестра Windows 2000
В Windows 2000 структура реестра значительно отличается от Windows 98. Устройство может использовать одну из трех возможных настроек:
настройки, специфические для устройства;
口 если настройки первого типа не существуют, берутся настройки последовательного порта, связанного с устройством;
если настроек последовательного порта не существует, берутся настройКИ, 'заШИТЫе** В Драйвер serial, sys.
В ветке реестра
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Serial
хранятся общие настройки поел ед о вател ьн ы х портов. Эта ветка содержит следующие ключи.
ForceFifoEnable —— если этот ключ имеет ненулевое значение, режим FIFO используется независимо от того, поддерживается ли он устройством. Если этот ключ равен 0, режим FIFO будет включен, только если драйвер обнаружит, что устройство поддерживает этот режим.
rxfifo——количество байтов в FI FO-буфере приемника, вызывающее прерывание (по умолчанию 8).
О TxFiFO — количество байтов в FIFO-буфере передатчика, вызывающее прерывание (по умолчанию 14).
Permitshare —— разрешение или запрещение использования прерывания несколькими устройствами одновременно. Если значение ненулевое, может использоваться разделяемое прерывание. По умолчанию 0.
Ветка реестра
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Serial\Enum
сохраняет текущее количество найденных портов и содержит следующие ключи.
О о, 1,...——последовательно нумерованные ключи найденных портов. Значение этих ключей равно идентификагору РпР соответствующего порта, например, КЛЮЧ 0 = "AGP工\PNPO5U1\1”,1 二,,ACPI\PNPO5O1\2,'.
Count ——число найденных портов.
Следует учитывать, что в этой ветке сохраняются только действительные порты, поэтому, например, виртуальный последовательный порт USB не будет отображен в этой ветке реестра.
А вот ветка реестра
HKEY_L0CAL_MACHINE\SYSTEM\ControlSet001\Services\Serenum\Enum
сохраняет все последовательные порты: и аппаратные, и виртуальные. Ключи этой ветки такие же, как и у предыдущей, но ссылки на порты могут содержать не только СОМ-порты, например:
0 = ACPI\PNP0501\l
1 = ACPI\PNP0501\2
口 2 二 USB\Vid_067b&Pid_2303\5&38488c91&0&l
Count = 3
Ветка реестра
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\SArial\Parameters сохраняет информацию об аппаратных свойствах порта. Если параметры не отличаются от стандартных, эта ветка будет пуста, в противном случае в ней могут присутствовать следующие параметры (мы приведем наиболее важные параметры, а их полный список можно найти в MSDN).
PortName — имя порта. По умолчанию порты используют имена СОМх, однако может быть установлено любое имя.
MuitiPortDevice ——если не ноль, то это порт на мультипортово!! карте.
口 ciockRate — обозначает частоту кварцевого генератора UAKT По умолчанию I 843 200 Гц.
При отключении устройства из данной конфшурации запись об этом производится в ветку
HKEY_CURRENT_CONFIG\System\CurrentControlSet\Enum\ACPI идентРпР
Например, при выключении порта СОМ1 будет создан ключ
НСС\...\ACPI\PNP0501\l\CSConfigFlags
Алгоритм Plug and Play для COM-портов
В этом разделе мы приведем стандартный алгоритм рабоi ы РпР 〃ля последовательных портов.
Инициализация порта
(Port initialization)
□ Система пытается открыть порт с помощью системных функции
口 Если порт занят (например, модем или мышь использукл jtot порт), то порт не будет конфигурироваться.
口 Если IRQ (Interrupt ReQuest, запрос прерывания), требуемое порту, уже занято другим драйвером, порт не будет конфигурировал вся
Обнаружение устройств
(Check for device)
Выход TxD устанавливается в состояние MARK IDI Е. 口 Устанавливается DTR = ON, RTS = OFF.
В течение (200 ± 35) мс ожидается появление сигнала DSR, которое указало бы на наличие устройства, подключенного к порту. В простейшем случае устройство имеет на разъеме перемычку DTR-DSR, обеспечивающую указанный ответ.
Если пауза ожидания завершена, a DSR = OFF, то система переходит к алгоритму ожидания отключения (си. разд. 5510).
Если устройство обнаружено, выполняется первая фаза установки устройства (см. разд. 553).
Установка устройства, фаза 1
(COM port Setup-1)
Порт программируется на режим 1200 бит/с, 7 бит данных, без паритета, I стоп-бит.
П У стана вл и вается DTR = OFF, RTS = OFF.
Отсчитывается пауза (200 ± 35) мс.
Устанавливается DTR = ON.
П Отсчитывается пауза (200 ± 35) мс.
Система переходит на первую фазу алгоритма ожидания ответа устройства (см. разд, 5.5.4).
Ожидание ответа, фаза 1
(Wait for response-1)
Устанавливается RTS = ON.
В течение (200 ± 35) мс ожидается приход первого символа от устройства. По приходе символа начинается прием идентификатора, т. е. выполнение алгоритма получения и дентифи катора (см. разд. 557).
口 Если за это время символ не пришел, выполняется вторая попытка опроса —алгоритм установки устройства, фаза 2 (см. разд. 5.5.5).
Установка устройства, фаза 2
(СОМ port Setup-2)
□ Устанавливастся DTR = OFF и RTS = OFF.
口 Выдерживается пауза (200 ± 35) мс.
П Система переходит к алгоритму ожидания ответа, фаза 2 (см. разд. 55.6).
Ожидание ответа, фаза 2
(Wait for response-2)
口 Устанавливаются DTR = ON и RTS = ON.
口 В течение (200 ± 35) мс ожидается приход первого символа от устройства, по приходе символа начинается прием идентификатора, т. е. выполнение алгоритма получения идентификатора (см. разд. 5.5.〃.
口 Если за это время символ не пришел м DSR = OFF, то система переходит к выполнению алгоритма проверки отключения устройства (см. разд. 5.5.Х). Если же DSR = ON, то выполняется переход в дежурное состояние (см. разд. 5.5.9).
Получение идентификатора
(Collect device ID)
Посимвольный прием идентификатора устройства имеет ограничения по тайм-ауту в Т1 = 200 240 мс на символ, а также общее ограничение времени получения Т2 = 2,2 с. Это позволяет принять строку длиной до 256 символов. Это офаничение получается простым вычислением:
(256 символов х 10 бит/символ) / (1200 бит/с) = 2,13 с.
Прием и де нти ф и катора выполняется по следующему набору правил.
口 Принимаются и складываются в буфер все получаемые символы.
Если после получения первого символа обнаружена ошибка четности или ошибка кадра, то система переходит в дежурный режим (см. разд. 559).
Если хотя бы один символ получен и тайм-аут ожидания символа Т1 завершен, выполняется проверка правильности полученного i щен г и фи катора. В случае корректного идентификатора система уведомляется о добавлении нового устройства. Иначе система переходит в дежурное состояние (см. разд. 559).
Если получен символ конца иденп[фикатора (будет описан далее), выполняется проверка правильности полученного иде!ггификатора. В случае корректного идентификатора система уведомляется о добавлении нового устройства. Иначе система переходит в дежурное состояние (см. разд. 5.5.9).
Если по завершению ожидания Т1 или Т2 символ начала идентификатора не получен, система переходит в дежурное состояние (см. разд. 5.5.9).
П Если по завершению ожидания Т2 символ завершения идентификатора не получен, система переходит в дежурное состояние (см. разд. 559).
Если DSR = OFF, система переходит к алгоритму проверки отключения (см. разд. 5.5.8).
Проверка отключения
(Verify Disconnect)
На этом шаге проверяется, действительно ли устройство удалено из системы. Используется для модемов и других устройств, которые кратковременно сбрасывают DSR, когда снимается DTR, например, при инициализации или сбросе устройства. Система понимает, что устройство шкрючено, если DSR = = OFF после некоторого тайм-аута.
Для проверки отключения выполняются следующие действия:
口 устанавливаются DTR = ON, RTS — OFF;
口 отрабатываегея пауза 5 с;
□ если после паузы DSR = ON, то система переходит к шггоритму дежурного состояния 化1 九 разд. 5.5.9);
口 если же после отработки паузы DSR = OFF, система переходит к алгоритму ожидания откггючения (см. разд. 5.510).
Дежурное состояние
(Connect Idle)
口 Устанавливаются DTR = ON, RTS = OFF.
Порт Iiporpaммируется на режим 300 бит/с, 7 бит данных, без паритета. 1 стоп-бит.
口 Если в этом состоянии обнаружится DSR = OFF, система уведомляется об отключении устройства и производится переход в режим ожидания отключения (см. разд. 5.5.10).
Необязательный шаг: система производит мониторинг порта для обнаружения устройств, не поддерж и ваю iu 11 х спецификацию РпР.
Состояние ожидания отключения
(Disconnect Idle)
Это состояние не обязательно, если система производит мониторинг порта для обнаружения динамически подключаемых устройств или устройств, не поддерживающих спецификацию РпР.
Система уведомляется об удалении устройств, если до этого они были обна- руженн для данного порта.
В этом режиме производятся следующие действия:
устанавливаются DTR = ON, RTS = OFF;
порт прог раммируется на 300 бит/с, 7 бит данных, без контроля четности, 1 стоп-бит;
П если в этом состоянии обнаружится DSR = 1, производится переход к алгоритму установки устройства, фаза 1 (см. р〃3d 5.5.4).
口 необязательный шаг: производится обнаружение других устройств, не поддерживаюших РпР.
Формат данных
для передачи РпР-идентификатора
Все РпР-совместимые устройства должны передавать свой идентификатор со скоростью 1200 бит/с (это наследство досталось от мыши). Формат данных должен быть совместим с 9-битовым форматом кадра приемника: один стартовый бит, 7 бит данных, 1 стоп-бит. Например, совместимыми являются такие форматы данных:
О 9-битовый кадр: 1 старт-бит, 7 бит данных, нет четности, 1 стоп-бит;
口 10-битовый кадр: 1 старт-бит, 7 бит данных, нет четности, 2 стоп-бита;
О 10-битовый кадр: 1 старт-бит, 7 бит данных, постоянная MARK-четность, 1 стоп-бит;
П 10-битовый кадр: 1 старт-бит, 8 бит данных с установленным в 0 старшим битом, нет контроля четности, 1 стоп-бит.
Эксперименты с совместимостью кадров мы будем проводить в разд 10,5.
Передача
РпР-идентификатора
Алгоритм опроса и передачу идентификатора каждое устройство осуществляет самостоятельно, согласно схемам, приведенным выше. Естественно, устройство должно реализовывать “обратную” часть этих алгоритмов.
Так, например, мышь действует coni ас но следующим правилам:
П передача DTR на DSR осуществляется аппаратно;
口 при начале опроса (т. е. DTR = ON и TD = MARK) производится ожидание RTS = ON;
口 когда RTS становится ON, мышь передает свой идентификатор:
О после передачи иденти фи кагора мышь переходит в обычное рабочее состояние.
Такой алгоритм может быть реализован и для других устройств, имеющих аппаратную перемычку RTS-DTR.
Алгоритм модемов сложнее:
при включении питания DSR устанавливается в состояние ON;
口 в режиме обычного функционирования молем контролирует сигналы RTS и DTR;
口 если модем обнаруживает состояние DTR = OFF, RTS = OFF и IDLE (т. е. отсугствие активных звонков), он переходит в режим ожидания установки DTR;
если DTR переходит в состояние ON, вы пол няются следующие шаги:
отсчитываются тайм-ауты Т1 = 15() мс и Т2 = 250 мс, в течение которых контролируется RTS;
если DTR становится OFF, модем снова переходит в состояние IDLE;
если RTS становится ON раньше, чем закончи!ся тайм-аут TL модем переходит в состояние IDLE;
если RTS = ON, Т1 завершился, а Т2 еще нет, то модем отправляет компьютеру свой РпP-идентификатор и переходит в режим IDLE;
если RTS = OFF и после завершения тайм-аута Т2, молем переходит в режим IDLE.
Похожий алгоритм должен быть реализован и ял я дру| их устройств, не имеющих аппаратной перемычки RTS-DTR.
Поля РпР-идентификатора
Устройства, поддерживающие стандарт РпР, должны передавать свой идентификатор в специальном формате. Поля передаваемой информации показаны в табл. 5.2. Первое поле зарезервировано дла старых устройств (например, мышей), которые самостоятельно генерируют один или несколько символов при включении питания. Следующие 4 ноля и последнее являются обязательными, а все оставшиеся —— необязателi,hыми. Если используются дополнитсяьные поля, контрольная сумма обязательна.
Для совместимости со старыми драй верами мыши все указатели, совместимые с этим ман и п ул яторо м, лол ж 11 ы уреза гь свои [апные до 6-битового набора символов для всех полей, исоючая поле other id. Следовательно, все такие поля должны содержать символы из набора $()()$3F или S20$5F. Выбор диапазона производится заданием символа Begin рпр. Естественно, символ End РпР также должен соответствовать выбранному диапазону.
Таблица 5.2. Поля РпP-идентификатора
Название поля
Размер
Необходимость
Описание
Other ID
<17
Нет
Зарезервировано для коротких не-РпР- идентификаторов. Может быть игнорировано системой
Begin PnP
1
Да
Начальный символ. Это либо $28 (символ "С*), либо $08
PnP Rev
2
Да
PnP-версия (например, $00 или $01)
EISA ID
3
Да
Уникальный идентификатор производителя
Product ID
4
Да
Уникальный идентификатор продукта
Extend
1
Нет
"\": или $5С или $ЗС
Serial
Number
8
Нет
Необязательный серийный номер устройства
Extend
1
Нет
"\": или $5С или $ЗС
Class
Name
<33
Нет
РпР-1лдентиф|лкатор класса устройства
Extend
1
Нет
"\": или $5С или $ЗС
Device ID
<41
Нет
Идентификатор совместимости драйверов
Extend
1
Нет
"\": или $5С или $ЗС
User Name
<41
Нет
Торговая марка продукта
Checksum
2
Да, если есть дополнительные поля
8-битовая арифметическая контрольная сумма всех байт, начиная с Begin PnP до End PnP включительно, исключая байты самой контрольной суммы. Две шестнадцатеричные цифры
End PnP
1
Да
Завершающий символ РпР-идентификатора. Это символ ")": или $29 или $09
Все дополнительные поля должны начинаться с символа "\”. Если дополнительное поле отсутствует, но следующее за ним указано, то выводится просто одиночный символ "\". Символ ”\” имеет код $ЗС для 6-битовой кодировки или $5С для 7-битовых кодов ASCII. Для пустых дополнительных полей в конце пакета символы ”\" могут не указываться.
Как мы уже считали, длина всего пакета ие должна превышать 256 символов.
Естественно, все и де нти ф и каци о 11 ные символы между Begin РпР и End РпР не должны меняться, иначе система будет обнаруживать все новые и новые устройства
Поле Other ID
Первое поле, длина которого не более 16 символов, может содержать необязательный идентификатор. Это поле не обязательно, оно оставлено для совместимости со старыми устройствами. Так, например, мышь генерирует посылку chmbsih "NT (код S4D) при подаче питания. Новые РпР-устройства могут не заполнять это поле. Единственное ограничение: эго поле не должно содержать шхентификагоров Begin РпР или End РпР (т. е. ни одного из кодов $08, $2$, $09, $29). Поле может включать 7-битовые символы “CR” и ”LF” для улучшения читаемости.
Поле Begin РпР
Поле Begin РпР маркирует начало РпР-идентификатора. Это либо 7-битовый символ $28, либо 6-битовый символ $08. В зависимости ОТ Begin РпР обработчик РпР будет ожидать соответсгвуюшего завершающего символа End РпР.
Поле РпР Rev
Два байта (точнее 12 бит) поля РпР Rev обозначают номер версии РпР. Двенадцать бит получаются из бит 5—0 первого символа, соединенных с бичами 5——0 второго символа. Для получения номера версии это число делился на 100 (см. листинг 17.1). Таким образом, номер версии будет находиться в границах от 0,00 ($00, $00) до 40,95 ($3F. $3F). При этом следует учитывать,, что это поле (так же как и все другие) не может содержать символов: Begin РпР Н End РпР, ПОЭТОМУ версия 1,05 (КОД $01, $29) И версия 1,37 (КОД $02, $09) не могут быть использованы.
Поле EISA ID
Каждый производитель оборудог^ания должен иметь уникальный трехбуквенный идентификатор. Если используется 6-битовый код (т. е. Begin РпР равно $()8), то нз кодов ASCII вычитаелся $20. Для получения таких идентификаторов следует обращаться в специальные сервисные центры.
Поле Product ID
Все ком мун и кацион н ые РпР-устройства должны иметь уникальный идентификатор, определяемый 16-битовым значением. Для 6-битового кода ше~ стнадцатеричные символы смещаются на $20.
Поле Extend
Значение поля Extend представляется символом обратного слеша ”\" (код $5С) для 7-битового кода и символом $ЗС для 6-битового. Указание такого символа означает, что будет использоваться поле, следующее за этим символом. Например, указание первого символа означает, что будет заполнено поле Serial Number. Если поле Serial Number не будет использоваться, а будет использовано поле ciassName, то необходимо указать два таких символа.
Если хотя бы одно дополнительное поле используется, необходимо указание контрольной суммы.
Поле Serial Number
Поле Serial Number является необязатеяьным. Устройства могут сообщать свой серийный номер. Это! номер может использоваться драйверами или файлами лицензий. Серийный номер должен состоять из 4 символов.
Поле Class Name
Поле class Name — необязательное поле. Оно должно быть не длиннее 32 символов. Передает имя класса устройства. Производители оборудования не могут добавлять свои имена. Возможные значения этого поля приводятся в табл. 5.3. В новых версиях Windows таблица имен расширена.
Таблица 5.3. Значения поля Class Name идентификатора РпР
Значение
ОписаниеВерсия
MODEM
Модем (Data, FAX, Voice и т. д.)
MOUSE
Мышь
PRINTER
Принтер
SYSTEM
Системное устройство. Для Windows 95/98/МЕWindows 95/
сюда же относятся UPS. В новых версиях98/МЕ
Windows выделено отдельное имя
BATTERY
Устройства питания (например, UPS)Windows NT/
2000/ХР
Таблица 5.3 (окончание)
Значение
Описание
Версия
Multifunction
Комбинированные карты, модемы
например, PCMCIA-
Multiportserial
Ports
Мультипортовые PnP-карты, но не подключаемые к ним устройства. Простая (старая) мультипорто- вая карта типа 16550 не включается в этот тип
СОМ- и LPT-порты
Поле Device ID
Необязательное поле Device id вюпсчает одну или более прок, разделенных символом запятой ”,“ (код $0С или S2C). Идснтифiщируci список совместимых драйверов для данною устройства. Каждый Device ie содержит три символа EISA ID И четыре символа Product ID.
Поле User Name
User Name ——необязательное поле, содержащее не более 40 символов. Содержит сведения о производителе продукта и о самом продукте. Это поле будет отображаться в списке устройств и в окне Обнаружено новое устройство. Строка не должна содержать символов End рпр, т. е. $09 и $29.
Для читаемости это поле может содержать символы "CR” и ^,LF', для 7-битового кода.
Поле Checksum
Поле Checksum представляет собой контрольную сумму. Если указано хотя бы одно дополнительное поле, указание контрольной суммы обязательно. Контрольная сумма считается как арифметическая сумма всех байт между Begin рпр II End рпр включительно без символов самой контрольной суммы (см. листинг 17.2).
Поле End РпР
Поле End рпр — символ завершения РпР-пдснтификаюра. Этот символ должен соответствовать выбраннсн! колировке. Если Begin рпр был $08, то End рпр должен быть $09, а для Begin рпр $28 должен использоваться код $29.
Пример PnP-идентификатора
Мы приведем несколько примеров PnP-идентификаторов. Следует отметить, что все поля вымышленные и к реальным устройствам отношения не имеют.
В табл. 5.4 показан пример Pn Р- и де нт и ф и катора для мышм без посылки дополнительных полей. Использован 6-битовый код.
Таблица 5.4. Пример идентифика тора РпР (мышь)
Код
Поле
Описание
4D
Other ID
Идентификатор старой мыши
08
Begin РпР
Начало PnP ID
00,01
PnP Rev
Версия РпР равна 0,01
21,20,23
EISA ID
"АМС" (Компания "Mouse")
11,12,13,14
Product ID
|,1234" (случайное число)
09
End PnP
Конец PnP ID
В табл. 5.5 приведен пример РпР-идентификатора некоторого модема, передающего дополнительные поля и использующего 7-битовую кодировку.
Таблица 5.5. Пример идентифика тора РпР (модем)
Код
Поле
Описание
—
Other ID
Поле пропускается, это не мышь
$28
Begin РпР
Начало PnP ID
$01 ,$24
РпР Rev
Версия РпР 1,00
$4D,$44,$43
EISA ID
"MDCn (Modem Design Company)
$30,$32,$38,$38
Product ID
"0288" (случайное число)
$5С,$30,$30,$33,$31, $34,$31,$35,$39
Serial #
"\00314159" (случайное число)
$5C,$4D,$4F,$44,$45, $4D
Class Name
"\MODEM" (класс "модем")
$5C,$4D,$44,$43,$30, $31,$341$34,$2С,$41, $54,$4D,$30,$30,$39, $36
Device ID
•^\MDC0144","ATM0096l,
(список совместимых драйверов)
$5С,$5А,$49,$50,$20, $32,$38.$38
User Name
"\ZIP 288" (имя модели)
Таблица 5.5 (окончание)
Код
Поле
Описание
$43,$34
Checksum
"С4" (контрольная сумма)
$29
End PnP
Конец PnP ID
INF-файл и его структура
Для установки драйвера устройства Windows ишет специальный файл с расширением inf Этот файл содержит всю информацию о действиях, которые необходимо произвести для установки драйвера: какие файлы нужно пере- нестм м зарегистрировать в системе, какие ветки реестра необходимо создать и т. д. Кроме того, этот же файл содержит информацию о действиях, которые нужно произвести прм удалении устройсиза из системы, а также дополнительную информацию о производителе устройства.
Структура INF-файла полностью совпадает с обычным INI-файлом: файл состоит из секций и некоторого набора ключей в них.
Конечно же, описывать все поля INF-файла мы не будем. Для этого потребовалась бы книга в несколько раз больше этой. Мы постарались выбрать тот минимум полей н их значений, который будет нам необходим прм создании м установке драйвера устройства в а?. /7
Секция Version
Секция version содержит основную информацию о INF-файле.
О Ключ signature = "signature-nameи описывает версию Windows, для которой применим этот INF-файл. Значение ключа не зависит от регистра, но должно точно соответствовать указанной ниже записи строк (знаки ”$" в начале и конце строки обязательны).
"$windows nt$” 一 операционные системы NT.
"$windows 95$" — системы Windows 95/98/ME.
”$chicago$” 一 любая версия Window.
П Ключ class = class-name описывает тип устройства. Значение может быть одной из стандартных строк, список которых мы привели в р〃з4 5.8.8. Следует отметить, что если значение class из INF-файла не совпадает со значением, переданным устройством, INF-файл все равно будет считаться правильным, а система установит устройство согласно значению из INF-файла.
Если указывается новый тип устройства, должен быть указан ключ ciassGuid, содержащий GUID нового типа устройства. Длина значения ключа class не может превышать 32 символа.
Ключ ClassGuid = {nnnnnrmn-nnnn-nnnn-nnnn-nnnnnnnnnrmn} описывает GU1D типа устройства. Обязательно указывается для нового типа устройства.
口 Ключ Provider = %iNF-creator% содержит имя компании ——разработчика INF-файла.
КЛЮЧ DriverVer = mm/dd/yyyy [, х . у. v. z] — ЭТО информация О версии драйвера, устанавливаемого INF-файлом. Поле mm/dd/yyyy указывает дату драйвера, а необязательные параметры х, у, v, z ——версию драйвера.
Если версия драйвера указывается, то каждая цифра должна быть в диапазоне от 0 до 65 535. Следует учитывать, что в версиях Windows ME, 2000 ii ХР это значение не учитывается при установке драйвера и служит только для отображения версии в Менеджере устройств (Device Manager). А в Windows ХР SP1, Windows Server 2003 и более поздних версиях это значение учитывается программой установки драйвера.
При установке или переустановке драйвера Windows сравнивает дату уже установленного драйвера и всех подходящих INF-файлов и выбирает наиболее новый (кроме Windows 98/МЕ, которые не учитывают дату при установке драйверов). Если ключ DriverVer не указан, система считает дату как оо/оо/оооо. Таким образом, файлы без этого ключа автоматически считаются наиболее старыми.
Секция Manufacturer
Секция Manufacturer описывает производителя одного или более устройств, устанавливаемых INF-файлом.
Секция имеет следующий формат:
[Manufacturer ]
manufacturer-identifier
[manufacturer-identifier]
[manufacturer-identifier]
Каждый ключ секции содержит информацию, описывающую одну модель устройства, и должен располагаться на отдельной строке. Допустимо использование одного из следующих форматов:
Manufacturer-name
%strkey%=models-section-name
%stгkey%=models-section-name[,TargetOSVersion][,TargetOSVersion]
В случае использования первого формата INF-файл должен содержать секцию с таким же именем (см. раз。. 5.9.4). Например:
;пример из файла DECPSMW4.INF
[Manufacturer]
"Digital"
[Digital]
•'Digital DEClaser 5100/Net"=D5100_MS.SPD,Digital_DEClaser_5100/Net
Второй формат аналогичен первому, но позволяет использовать строки для перевода, указываемые в секции string. Например:
;пример из файла CXPDFPCI.INF
[Manufacturer]
%Stringl%=DIGI
[DIGI]
%String2%=CxpPCIlST.Install,MF\DIGIPCIlST_DEVO
[Strings]
Stringl="Digi International**
String2="Digi DataFire PCI 1 S/T (CXP)”
Естественно, в секции strings должны быть определены все используемые ССЫЛКИ %strkey%.
Третий формат описания доступен, только начиная с версии Windows ХР. Он позволяет указывать список версий и типов Windows, для которых предназначен данный INF-файл. Программа установки будет выбирать тот драйвер, который наиболее близко подходит к перечисленным описаниям.
Формат строки TargetOSVersion СЛедуюШИЙ:
NT[Architecture][.[OSMajorVersion][.[OSMinorVersion][.[ProductType] [.SuiteMask]]]]
Кратко рассмотрим поля этой строки:
П идентификатор nt указывает, что это описание поддеpxiiвается только версиями Windows семейства NT;
П поле Architecture описывает аппаратную платформу. Еслн указывается, должен быть или х86 или ia64;
口 поле OSMajorVersion задает старший номер версии операционной системы. Для Windows ХР это номер 5;
口 поле OSMinorVersion задает младший номер версии операционной системы. Для Windows ХР это номер 1;
口 поле productType может быть одной из констант ver_nt_xxx, определенных в winnt.h, например:
0x0000001 = VER _NT_WORKSTATI0N;
0x0000002 = VER_NT_DOMAIN_CONTROLLER;
0x0000003 = VER_NT_SERVER;
口 поле suiteMask — это маска из значений констант ver_suite_xxxx, определенных в winnt.h, например:
0x00000001 = VER_SUITE_SMALLBUSINESS;
0x00000002 = VER_SUITE_ENTERPRISE;
0x00000004 = VER_ SUITE_BACKOFFICE;
0x00000008 = VER_SUITE_COMMUNICATIONS;
0x00000010 二 VER_SUITE_TERMINAL;
0x00000020 = VER_SUITE_SMALLBUSINESS_RESTRICTED;
0x0000004 0 = VER_SUITE__EMBEDDEDNT;
0x00000080 = VER_SUITE_DATACENTER;
0x00000100 = VER_SUITE_SINGLEUSERTS;
0x00000200 = VER_SUITE_PERSONAL;
• 0x00000400• 0x00000400VER_SUITE_SERVERAPPLIANCE.
• 0x00000400
• 0x00000400
Если ключи секции Manufacturer содержат определения версий ОС, то и платформы, и секции, на которые ссылаются указанные ключи, должны содержать те же определения, например:
[Manufacturer]
%MyNam.e% = MyName, NTx8 6.5.1
[MyName]
%MyDev% = InstallA, hwid
[M^4SJam.e. NTx86.5.1]
%MyDev% = InstallB,hwid
[installA.ntx86]; Windows 2000 (NT4-x86 будет также пытаться
;читать эту секцию)
[InstallA]; Win98/WinME (Win95 также будет пытаться
;читать эту секцию)
[InstallB][InstallB]; Windows ХР и позже, и только х86
[InstallB]
[InstallB]
Секция DestinationDirs
Секция DestinationDirs указывает одну или несколько директорий для копирования, удаления и переимсног^ания файлов.
Эта секция необходима в INF-файле, если он содержит либо ключ CopyFiles, либо ссылку НН секции CopyFiles, DelFiles ИЛИ RenFiles.
Каждая директория описывается на отдельной строке секции н имеет формат:
[DestinationDirs]
[DefaultDestDir—dirid[,subdir]]
[file-list-section=dirid[,subdir]]
Ключ DefaultDescDir
Ключ DefaultDescDir указывает директорию по умолчанию лл5】 копирования, удаления и переименования файлов, которые не описаны в списке file-list-section.
Ключи file4ist-section
Ключи file-list-sect ion перечисляют имена файлов и их директории, если нужно установить директории, отличные ОТ DefaultDescDir.
Ключ dirid
Ключ dirid указывает директорию, в которой будет находиться указанный файл или файлы. Это может быть или абсолютный путь или номер (идентификатор) директории. Абсолютный путь может ссылаться на секцию String.
Идентификатор директории представляет собой целое число. Значения в диапазоне от —1 до 32 767 зарезервированы (табл. 5.6). Кроме того, следует учитывать, что в целях совместимости с Windows 98/МЕ значение 65 535 приравнивается к —1.
Таблица 5.6. Таблица значение dirid
Значение
Описание
1
SourceDriveXpathname (указывает директорию, из которой был установлен INF-файл)
10
Windows-директория, т. е. %windir%.
11
Системная директория, т. е. %windir%\system32 для NT-систем, и %windir%\system для Windows 9x/ME
12
Директория драйверов, т. e. %windir%\system32\drivers для NT-систем и %windir%\system\loSubsys для Windows 9x/ME
Таблица 5.6 (окончание)
Значение
Описание
17
Директория INF-файлов, т. е. %windir%\INF
18
Директория файлов помощи, т. е. Help directory %windir%\HELP
20
Директория шрифтов
24
Системный диск, т. е. если Windows установлена в папку C:\winnt, то это будет "С:\"
30
Корневая директория загрузочного диска (для NT-систем необязательно совпадает с ID24)
50
Системная директория для NT-систем, т. е. %windir%\system (только для NT)
53
Директория пользовательского профиля
54
Директория, где расположены файлы ntldr.exe и osloader.exe (для NT-систем)
-1
Абсолютный путь
16406
All Users\Start Menu
16407
All Users\Start Menu\Programs
16408
All Users\Start Menu\Programs\Startup
16409
All Users\Desktop
16415
All Users\Favorites
16419
All Users\Application Data
16422
Program Files
16427
Program Files\Common
16429
16430
All Users\Templates
All Users\Documents
Ключ subdir
Ключ subdir обозначает директорию относительно dirid, наример, ;пример из файла ICW97.INF [DestinationDirs]
CopyHELP
CopySYS CopyINF DeleteICW2
=18;LDID_HELP
=11;LDID_SYS
二 17;LDID_INF
=24r %ProgramFiles%\%OLD_ICWDIR%
Обратите внимание, что поддиректория в dirid указывается через запятую, а не через слэш.
Секция описания модели
Секции описания моделей должны соответствовать ссылкам на модели из секции Manufacturer (см. разд. 5.9.2). Формат строк этой секции имеет вид:
Device-description=install-section-name,hw-id[,compatible-id...]
П Поле device-description ——это любая уникальная строка описания или ссылка на строку в секции string.
Поле instа. 1.1 - section-паше содержит имя секции, описывяющей уст- ройство.
П Поле hw-id содержит строку, соответствующуIO Hardware ID в идентификаторе РпР и может иметь один из нижеперечисленных форматов.
Enumerator\device-id — обычный формат для устройства, устанавливаемого по спецификации РпР. Например, serenum\ pvao о о 1.
*device-id ——указывает, что устройство поддерживается различными сервисами. Например, *pnpofoi идентифицирует Microsift-мышь, которая имеет совместимый идентификатор serenum'、pnpofoi.
Device-class-id — спецификатор шины, как он описан в аппаратной спецификации.
口 В поле comparibie-id содержатся один или несколько совместимых с hw-id типов устройств, разделенных запятой.
Рассмотрим пример, который мы будем использовать в практической части КНИГИ (см. Z2. 17). Секция Manufacturer ссылается на секцию CompanyName, которая содержит описание устройств, подключаемых к последовательному порту (serenum) и имеющих и де нти ф и каторы pvao ooi и pvaoooo. Для таких устройств описание драйвера будет находиться в секции MyDevice SECT工on.
[Manufacturer]
%Mfg%=CorrpanyName
[CompanyName]
%MyDeviceStr%=MyDevice_SECTION, SERENUM\PVA0001, PVAOOOO
[MyDevi c JSECT ION ]
CopyFiles= MyDevice.CopyFiles
AddReg=MyDevice. AddReg
DelReg=MyDevice.DelReg
LogConf ig=HyDevi ce.Config
Секции xxx.AddReg и xxx. De I Reg
Эти секции описывают действия с реестром при установке и удалении устройства. Ссылка на имена секций дается из секции описания модели (см. разд. 5.9.4).
Формат строк этой секции имеет вид:
Reg-root,[subkey],[value-entry-name],[flags],[value]
Поле reg-root — идентификатор корневой ветки реестра. Может быть одним из обозначений:
HKCR = HKEY_CLASSES_ROOT:
HKCU = HKEY_CURRENT_USER;
HKLM = HKEY_LOCAL_MACH工NE;
HKU 二 HKEY—USERS.
Поле subkey содержит имя ветки реестра, относительно reg-root, или ссылку на строку секции string.
口 В поле value-entгу-паше содержится имя ключа, создаваемого в реестре в ветке reg-root\subkey. Может быть или строкой, заключенной в кавычки, или ссылкой на значение из секции string.
Поле flags —— необязательное, обозначает тип и свойства добавляемого значения. Значение представляет собой маску из констант flg_xxx (табл. 5.7). Мы приведем самые существенные из этих констант.
Таблица 5.7. Таблица флагов секции AddReg (DelReg)
Флаг секции AddReg (DelReg)
Описание флага
$00000001
Создать как "Бинарные данные"
FLG_ADDREG_B工NVALUETYPE
$00000010
Создать subkey, но игнорировать
FLG_ADDREG_KEYONLY
value-entry-name и его значение
$00000020
Переписать значение value-entry-name,
FLG ADDREG OVERWRITEONLY
если оно существует. Если такого значения
--——
еще нет, не создавать
$00001000
Создание 64-битового значения (Windows ХР
FLG_ADDREG_6 4 BITKEY
и выше)
$00000000
Создание значения типа REG_SZ. Этот флаг
FLG ADDREG TYPE SZ
является значением по умолчанию, если ключ
———
flags не указан
92
Часть /. Протоколы и интерфейсы
Таблица 5.7 (окончание)
Флаг секции AddReg (DelReg)
Описание флага
$00010000
Создание значения типа REG MULTI SZ. Для
FLG ADDREG TYPE MULTI SZ
таких значений не требуется код NULL для за-
вершения строки
$00020000
FLG—ADDRE G_TYPE_EXPAND_SZ
Добавление значения типа REG_EXPAND_sZ
$00010001
FLG_ADDREG_TYPE一DWORD
Добавление значения типа REG_DWORD
$00020001
Добавление значения типа REG NONE (только
FLG_ADDREG_TYPE_NONE
Windows 2000)
Поле value содержит значение ключа реестра reg-root \ subkey \ vaiue-enntry-name и должно соответствовать типу, указанному флагами flags.
; пример из файла TAPI.INF [add.reg]
HKU,Software\Microsoft\Windows\CurrentVersion\Telephony,,,
HKU,Software\Microsoft\Windows\CurrentVersion\Telephony\HandoffPriorities, "RequestMakeCall", 2, "DIALER. EXE °
HKLM,Software\Microsoft\Windows\CurrentVersion\Telephony\Locations, "Next工D”,3, 01,00, 00, 00
HKLM,Software\Microsoft\Windows\CurrentVersion\RunOnce,TapiSetup2,, "tap iupr.exe'1
Секция xxx.LogConfig
Секция xxx.LogConfig описывает системные ресурсы, требуемые драйвером.
Ссылка на имя этой секции дается из секции описания модели (см. разд. 5.9.4).
В этой секции могут указываться ключи (мы снова выбрали наиболее используемые ключи):
口 DHAConfig — описание ресурсов DMA;
П lOConfig — описание ресурсов портов;
MemConfiq — описание ресурсов памяти;
iRQConfig — описание ресурсов IRQ.
Пример:
[MyDevicel.LogConfig]
DMAConfig=Oz 1
I0Config~3bc-3be(3ff::),378-37a(3ff::),278-27a(3ff::)
[MyDevice2.LogConfig]
I0Config=8@100-3ff%fff8 (3ff::)
MemConfig=4000@C8000-EFFFF%FFFFC000
[MyDevice3.LogConfig]
lOConfig=l10-11F(3FF::)f130-13F(3FF::) , 150-15F(3FF::)
IRQConfig=3, 4
MemConfig=4000GC0000-DFFFF%FFFFC000
Подробное описание всех ключей можно найти в MSDN.
Секция xxx.CopyFiles
Секция xxx.CopyFiles описывает операции над файлами, которые требуются для работы драйвера. Ссылка на имя этой секции дается из секции описания модели (см. разд. 594).
Секция содержит или имена файлов, или ссылку на секцию с именами файлов.
Каждое имя файла задается строкой вида:
Dest-file-name[,source-file-name][,temp-file-name][,flag]
口 Поле dest-file-name задает имя конечного файла. Если секция source-file-name не указана, то запаег и имя исходного файла.
口 Поле source-file-name задает имя исходного файла. Если для операции копирования файлов имена исходного и конечного файлов одинаковые, то это поле можно пропустить.
□ Поле temp-file-name задает имя промежуточного файла, которое будет использовано, если конечный файл занят в дгшный момент времени. Используется только в Windows 95/98/МЕ, т. к. Windows NT и выше создают временные файлы автоматически. Операция над файлом будет выполнена при перезагрузке Windows.
О Поле flag —— необязательное поле, представленное в шестнадцатеричном или десятичном виде. Является маской одного или нескольких систем- ных флагов copyflg ххх (табл. 5.8).
Таблица 5.8. Таблица флагов секции Copy Files
Флаги секции CopyFiles
Описание флагов
$00000400
Копировать файл только в том случае, если он
COPY FLG_REPLACEONLY
уже существует в конечной директории
$00000800
Копировать исходный файл без распаковки
COPYFLG_NODECOMP
$00000008
Копировать файл в файл temp и использовать
COPYFLG_FORCE_FILE_工N_U SE
его только после рестарта системы (эмуляция поведения занятости файла)
$00000010
Не заменять файл, если он уже существует.
COPYFLG_NO_OVERWRITE
Этот флаг не может использоваться с другими флагами
$00001000
После выполнения операции требовать переза-
COPYFLG_REPLACE_BOOT_FILE
грузки системы
$00000020
Не переписывать файл в конечной директории,
COPYFLG_NO_VERSION_DIALOG
если существующий файл более новый, чем копируемый
$00000004
Игнорировать версию файла. Переписывать
COPYFLG—NOVERS工ONCHECK
конечный файл независимо от его версии
$00000002
Не позволять пользователю отказаться от ко-
COPYFLG_NOSKIP
пирования файла
Секция Strings
Секция strings используется для создания INF-файлов на нескольких язы- ках. Другие секции могут ссылаться на ключи этой секции с помощью знаков “%". Строка, заключенная в знаки "%", означает подстановку значения переменной из этой секции.
Значения полей этой секции могут быть обычными строками или строками, заключенными в кавычки, если они содержат существенные пробелы до или после строки.
Для NT-платформы секция strings может содержать индекс языка, для которого указываются строки. Индекс представляет собой объединения константы LANG_xxx И SUBLANG—ХХХ. Например,
[Strings]; Обычный набор строк
DiskName=,,My Excellent Software*'
LocaleSubDir="Englishn
[Strings.0407]; Набор строк для немецкого языка (0407)
DiskName=”Meire ausgezeichnete Software1'
LocaleSubDir=MGermanM
для русского языка (0419)для русского языка (0419)[Strings.04191; набор строк
для русского языка (0419)
для русского языка (0419)
[Strings.0422]; набор строк для украинского языка (0422)
Связи секций
Собрав все перекрестные ссылки, мы получим картину связей между основными секциями. Итак, секция version является обязательной в INF-файле. Секция Manufacturer содержит ссылку на секцию описания модели, которая в свою очередь содержит ссылки на секции для конкретных устройств с заданными серийными номерами. Секция описания устройства содержит ССЫЛКИ на секции CopyFiles, AddReg, DeIReg И LogConfig. Кроме ТОГО, любая секция может содержать ссылку на строки из секции strings (рис. 5.3).
Рис. 5.3. Связи основных секций INF-файлаРис. 5.3. Связи основных секций INF-файла
Рис. 5.3. Связи основных секций INF-файла
Рис. 5.3. Связи основных секций INF-файла
ЧАСТЬ II
Практика
ПРОГРАММИРОВАНИЯ
Глава 6
Использование сервисов BIOS
В этой главе мы продемонстрируем простейший пример приема и передачи данных. Мы создадим программы приемника и передатчика и научимся передавать данные.
Подготовка
Последовательная связь подразумевает наличие, как минимум, приемника и передатчика. Конечно, можно использовать два компьютера, но мы поступим проще. Так как Windows 2000 не запрещает прямого обращения к портам из DOS-программ, то мы будем использовать две консоли (например, два запущенных Norton Commander или Dos Navigator). В первом окне мы будем работать с программой одного абонента, использующего порт СОМ 1, а во втором —с программой второго абонента, использующего порт COM2. Порт СОМ1 мы соединим с портом COM2 с помощью нуль-модсмного кабеля.
Итак, нам потребуются:
О персональный компьютер с двумя СОМ-портами;
口 операционная система Windows 2000 или Windows 95/98;
желательно наличие файловой оболочки типа Norton Commander;
компиляторы Pascal (ВРС, версии выше 5 для DOS, и Delphi, версии выше 3 для Windows);
О нуль-модемный кабель для соединения портов (см. рис. 1.3).
Соединим порт СОМ1 с COM2 и попробуем передать данные с одного порта на другой.
В Windows 2000 возможна маленькая неприятность: открыв две DOS-консоли, нам придется переключаться между ними, т. к. Windows может останавливать выполнение DOS-программ, если они не активны. Таким образом, отправив байт с одной консоли, нам нужно переключиться на вторую и получить его. Конечно, можно настроить ярлыки для запуска наших программ, но для проведения таких простых тестов это лишняя забота.
Первая программа последовательной связи
Функции, которыми мы будем пользоваться, подробно описаны в третьей части книги, в 乙?. /8.
Независимо от того, будем мы передавать или принимать данные, необходимо сначала инициализировать порт. Инициализация порта подразумевает задание следующих параметров:
П скорость передачи (точнее делитель);
П число битов данных;
П контроль четности;
口 число стоп-битов.
Передачу данных мы будем производить через порт COM I, а принимать данные ——с порта COM2. Подробности можно найти в комментариях к кодам листингов 6 1 и 62
Листинг 6.1. Программа передачи данных с помощью 工nt:14H
.»;..*;d,“““》.s.s;:;.:二....“■.,“.•.““..;*;“.““.;.《;.、.《.,♦,“♦♦.::“..♦... J..:;;3z_W. *_,_*;?; = ____,*■“;У ,“♦.,、"_“...,』,《:♦♦.…
Uses Crt;
Var
В : Byte;
Begin
WriteLn(*Передача байта через сервис BIOS *);
WriteLn(*Порт COM11);
Asm
Mov Ah, 0{код функции 一 инициализация порта}
Mov Dx, 0{номер порта — СОМ1}
Mov Al, 227 {11100011b=9600, нет четности, 1 стоп-бит} {8 бит данных}
工NT 14Н
End;
While not KeyPressed do begin
Write(* Введите байт:,); ReadLn(В);
Asm
Mov Ah, 1 {код функции — передача байта}
Mov Dx, 0 {номер порта — СОМ1}
Mov А1, В {передаваемый байт }
INT 14Н
End;
End;
End.
i Листинг6J2. Программа чтения данных с помощью юткн
Uses Crt;
Var S, В : Byte;
Begin
WriteLn(1Чтение данных с порта COM2 *);
Asm
Mov Ah, 0
Mov Dx, 1
Mov Al, 227
{номер функции — инициализация}
{номер порта — COM2}
{11100011Ь=9600, нет четности, 1 стоп-бит}
{8 бит данных}
INT 14H
End;
While not KeyPressed do begin
Asm
Mov Ah, 3
Mov Dx, 1
INT 14H
{номер функции - статус порта } {номер порта - COM2}
Mov S, AH End;
If (S and
Asm
1) = 1 then begin {нулевой бит - есть данные }
Mov Ah, Mov Dx, 工NT 14H
2 {номер функции - чтение данных}
1 {номер порта - COM2}
Mov В, А1 {принятый байт}
Mov S, Ah {код ошибки}
End;
If S = 0 then begin {нет ошибок} WriteLn(В); {отобразим полученный байт}
End;
End;
End;
While KeyPressed do {очистим клавиатурный буфер}
ReadKey;
End.
Из первой консоли запустим программу чтения данных, а из второй — программу передачи. После ввода байта во второй консоли мы должны увидеть его в первой.
Обратите внимание на задание номера порта. В первой программе в регистр Dx записывается 0, означающий выбор С0М1, а во второй программе записывается 1, означающая выбор COM2.
Больше ничего сложного в нашей первой программе нет. К сожалению, простота реализации оборачивается другой стороной: обмен данными возможен только на скоростях до 9600 бит/с.
Другие функции BIOS
Как видно из листинга программ 6.1 и 6.2, мы воспользовались тремя функциями сервиса INT14H: функциями оо, 01 и 02. Собственно эти три функции и составляют практически весь набор функциональности INT14H. Для System/2 существует еще две функции с номерами 04 и 05. Их описание можно найти в гл. 18.
Кроме сервиса int14h, BIOS предоставляет еще четыре функции для последовательного ввода/вывода. Это функции озн, смн, зен и 4он прерывания int21h. Они еще более ограничены, чем INT14H — они работают только с С0М1 и только с текущими настройками порта.
Это редко используемый способ работы с портом, и мы не будем рассматривать его в нашей книге. Заметим только, что при необходимости работы с COM2 нужно переставить базовые адреса в области BIOS, например, как показано в листинге 6.3.
:Листинг 6.3. Обмен базовых адресов СОМ1 и COM2
обмен базовых адресов для С0М1 и COM2
MOV
AX,40H;ES указывает на область данных BIOS
MOV
ES,AX;
MOV
DX,ES:[0];помещаем 1-й базовый адрес в DX
MOV
АХ,ES:[2];помещаем 2-й базовый адрес в АХ
MOV
ES:[0],АХ;обмениваем адреса
MOV
ES:[2],DX;
Описание этих функций можно найти в третьей части книги, в гл. /&
Глава 7
Прямое программирование портов
К сожалению, сервис BIOS весьма ограничен: мы нс можем использовать скорость обмена выше 9600 бит/с, а ведь максимальная допустимая скорость самой UART-микросхемы составляет 115 200 бмт/с. Избавиться от ограничений позволит прямое программирование портов, без использования функций BIOS. С помощью программ, описанных в этой главе, мы организуем последовательную связь на скорости 28 80() бит/с.
Коммуникационные порты
Ком мун икаи и он ные порты занимают в пространстве ввода/вывода по восемь смежных 8-битовых регистров и располагаются по стандартным базовым адресам. Базовый адрес —— это 2-байтовый адрес порта, который является младшим из группы адресов портов, дающих доступ к UART.
Раньше на всех машинах COM I имел базовый адрес 3F8H, а COM2 — 2F8H. Новые версии BIOS позволяют задавать этот адрес самостоятельно.
В процессе начального тестирования POST BIOS проверяет наличие последовательных портов по стандартным адресам и помешает базовые адреса портов в ячейки данных BIOS. Нулевое значение адреса является признаком отсутствия порта с данным номером.
Обнаруженные порты инициализируются на скорость обмена 2400 бит/с, 7 бит данных с контролем четности и I стоп-бит. Управляющие сигналы интерфейса DTR и RTS переводятся в состояние 'выключено1' (положительное напряжение). Тайм-аут порта по умолчанию устанавливастся в одну секунду.
Полное описание портов можно найти в л. 19.
Программа определения наличия СОМ-портов
Как уже говорилось, при инициализации системы BIOS заносит базовые адреса в специальную область данных, находящуюся по адресу 0:40.
Программа, показанная в листинге 7.1, читает таблицу базовых адресов и отображает наличие последовательных портов компьютера.
督*内产:…4…1r:К\.?.:串…劈"?♦*:"-丁.…屮峭,妙蟲..…...「.:..:"*:?";…#W.q*炉".T-FY5.еe.".....、.然…丁…….*………..;,"…"0,**""…51M,♦*……,
^Листинг 7.1. Отображение наличия СОМ^лортов и их базовых адресов
{ Возвращает базовый адрес порта с номером Portindex } Function GetBaseAdr (PortIndex : Byte) : Word;
Var LowAdr : Word;
Begin
{ вычисляем младшую часть адреса в таблице }
LowAdr := (PortIndex-1)*2;
{ получаем базовый адрес порта из таблицы }
GetBaseAdr: = MemW[$0040:LowAdr];
End;
Var
Port Index : Byte;
BaseAdr: Word;
Begin
{ Опрос базовых адресов 4 портов }
For Portlndex:= 1 to 4 do begin
{ Получить базовый адрес}
BaseAdr:= GetBaseAdr(PortIndex);
{ Анализируем базовый адрес }
If BaseAdr = 0 then
WriteLn{1COM *, PortIndex,* не обнаружен1)
Else
WriteLn(базовый адрес COM1,PortIndex,1 равен ', Long2Hex(BaseAdr));
End;
End.
Важно!
Мы будем часто пользоваться такими функциями, как Long2Hex, Byte2Hex и др. Для экономии места все эти функции удалены из листингов и приводятся в прил. 2.
Результат запуска этой программа может быть, например, такой:
\SOURCE\4_l>ports. ехе
Базовый адрес СОМ1 равенБазовый адрес СОМ1 равен03 F803 F8Базовый адрес COM2 равенБазовый адрес COM2 равен02F802F8
Базовый адрес СОМ1 равен
Базовый адрес СОМ1 равен
03 F8
03 F8
Базовый адрес COM2 равен
Базовый адрес COM2 равен
02F8
02F8
Базовый адрес COM3 равен 03Е8
COM4 не обнаружен
Новые версии BIOS позволяют изменять базовые адреса портов, поэтому базовые адреса должны считываться каждый раз при запуске программа Использование констант в качестве базовых адресов может привести к нера- ботоспособности программы, если она будет запущена на компьютере с нестандартно сконфигурированными адресами.
Обходим ограничения BIOS
Итак, мы умеем определить базовый адрес нужного нам порта. Попробуем создать программу для приема и передачи данных, используя непосредственное обращение к порту.
Будем рассматривать некоторый порт с базовым адресом BaseAdr. Начнем с инициализации порта. Для задания конфигурации порта нам надо:
口 установить специальный бит dlab, открывающий доступ к делителю скорости порта;
口 записать младшую часть делителя скорости в порт [BaseAdr+О];
записать старшую часть делителя скорости в порт [BaseAdr+i];
口 сбросить бит DLAB;
П записать конфигурацию В порт [BaseAdr+3].
Установка бита DLAB производится записью числа $80 В порт [BaseAdr+3], т. е. установкой старшего бита этого порта. Заметьте, что запись конфигурации производится в тот же порт, а значит, два последних шага можно объединить, просто записав конфигурацию В порт [BaseAdr+З] (листинг 7.2).
;Листинг 72 Инициализация порта, имеющего базовый адрес BaseAdr
Var { Будет хранить базовый адрес порта }
BaseAdr : Word;
{Инициализация порта с номером Comlndex}
{ Comlndex — номер порта}
{ Speed— скорость в бод}
{ Params — конфигурация порта, согласно формату битов LCR }
(Возвращает False, если порт не обнаружен}
Function InitCOM(Comlndex : Byte; Speed : Longint; Params : Byte): Boolean;
var Freq : Longint;
begin
InitCOM:= True;
Freq := 115200 div Speed;
{ Вычисляем базовый адрес порта }
BaseAdr: = GetBaseAdr (Comlndex);
If BaseAdr = 0 then begin
WriteLn(* Порт ', Comlndex,' не обнаружен!1);
InitCOM:- False; {вернем ошибку}
Exit;
End;
{Адресуем делитель порта с помощью установки DLAB=1} Port[BaseAdr+3]:= $80;
{Устанавливаем младшую часть делителя}
Port [BaseAdr+0] := Freq and $00FF;
{Устанавливаем старшую часть делителя}
Port [BaseAdr+1]:= Freq shr 8;
{Сбрасываем DLAB и прописываем конфигурацию}
Port [BaseAdr+3 ] : = Params;
end;
Мы не стали усложнять программу константами настройки порта, а просто передаем параметр Param, содержащий нужные нам биты настройки. Значение этого параметра совпадает с форматом порта lcr, описанного в гл. 19. Например, значение 3 (битовый код <0000 0011>) подразумевает настройку порта на следующие параметры:
контроль четности отсутствует;
П один стоп-бит;
口 8 бит данных.
Вычисление делителя производится по формуле:
Делитель = 115 200 / Скорость
Смысл этой формулы объясняется в справочной части, в гл. 19.
Чтение и передача данных очень просты и выполняются чтением и записью порта [BaseAdr+O] при сброшенном бите dlab, как показано в листинге 7.3.
Листинг 7*3・ Чтение и передача данных в порт с базовым адресом BaseAdr 4
Function ReadCOM : Byte; assembler;
Asm
Mov Dx, BaseAdr
In Al, Dx
End;
Procedure WriteCOM(B : Byte); assembler;
Asm
Mov Dx, BaseAdr
Mov Al, В
Out Dx, Al
End;
Недостаток вышеприведенного кода очевиден: мы не обрабатываем никаких ошибок. По результатам вызова ReadCom мы не можем определить, действительно ли мы прочитали байт, и прочитан ли байт без ошибок.
Чтобы исправить этот недостаток, сделаем 'оболочки" для функций чтения и записи, добавив в них параметры ожидания и обработку ошибок. Для простоты мы будем задавать время ожидания не в реальных величинах, а в "циклах ожидания". Чаще всего этого достаточно.
Готовность данных для чтения нам дает бит dr, а готовность передатчика определяется по биту thre порта [BaseAdr+5]. В листинге 7.4 приведен новый код наших функций.
Листинг 7Л. Чтение и передача данных в порт с базовым адресом BaseAdr с определением тайм-аутов и кодов ошибок
{ Возвращает бит DR порта LSR } Function GetDR : Boolean; assembler;
Asm
Mov Dx, BaseAdr
Add Dx, 5
In Al, Dx
And Al, L End;
[Возвращает биты ошибок порта LSR}
{ Бит 4 — обрыв линии}
{ Бит 3 — ошибка кадра (неверный стоп-бит) } { Бит 2 — ошибка четности}
{ Бит 1 — переполнение (потеря символа)}
Function GetErr : Byte; assembler; Asm
Mov Dx, BaseAdr
Add Dx, 5
In Al, Dx
And Al, 1EH {0001 1110b}
End;
{Возвращает бит THRE порта LSR } Function Get SR : Boolean; assembler; Asm
Mov Dx, BaseAdr
Add Dx, 5
In Al, Dx
And Al, 2OH
End;
{Чтение байта с порта с тайм-аутом}
{ В — прочитанный байт)
{ Wait — время ожидания байта}
{Возвращает True, если байт реально прочитан}
Function ReadData(var В : Byte; Wait : LongInt): Boolean; Var Result : Boolean; w : LongInt;
Begin
w:= 0; Result:= False;
For w:= 1 to Wait do begin {ожидание...}
If GetDR then begin {проверяем доступность данных }
B:= ReadCOM; {читаем байт с порта}
Result:= (GetErr = 0); {проверяем код ошибки}
Break;
End;
End;
ReadData:= Result;
End;
{Запись байта в порта с тайм-аутом}
{ В— передаваемый байт}
{ Wait — время ожидания готовности переда^ика}
{Возвращает True, если байт передан}
Function WriteData(В : Byte; Wait : LongInt): Boolean; Var Result : Boolean; w : LongInt;
Begin
w:= 0; Result:= False;
For w:= 1 to Wait do begin {ожидание...} If GetSR then begin {передатчик готов?} WriteCOM(В); {передаем байт} Result:= True;
Break;
End;
End;
WriteData:= Result;
End;
Если прочитанный байт должен иметь строго определенное значение (например, начало или завершение посылки), удобно использовать функцию, возвращающую True только при успешном чтении нужного байта (листинг 7.5).
Листинг 7.5. Чтение байта с проверкой
{Вернет True, если байт прочитан успешно и равен ChB}
Function CheckReadData(ChB : Byte; Wait : Longlnt) : Boolean;
Var В : Byte;
Begin
CheckReadData:= False;
If ReadData(B, Wait) then begin
CheckReadData:= (B = ChB);
End;
End;
Все эти функции мы соберем в один модуль rs232dos, которым будем пользоваться в следующем разделе. Исходный код получившегося модуля можно найти на прилагаемом компакт-диске.
Чтение и запись
с помощью модуля RS232DOS
Модифицируем наши программы приемника и передатчика таким образом, чтобы вместо сервисов BIOS они использовали наш модуль RS232DOS с прямым программированием портов. Для этого достаточно заменить функции инициализации, чтения и записи данных. Результат показан в листингах 7.6 и 7.7.
Листинг 7.6. Чтение данных с порта COM2 с помощью RS232DOS
Uses Crt, RS232Dos;
Var В : Byte;
Begin
WriteLn (' Читаем данные с COM2 ');
InitCCM(2, 28800, 3);
While not KeyPressed do begin
If ReadData(B, 2000) then begin WriteLn (B);
End;
End;
While KeyPressed do ReadKey;
End.
Листинг 77 Передача данных с порта СОМ1 с помощью rs232dos
Uses Crt, RS232Dos;
Var
В : Byte;
Begin
WriteLn(* Запись данных в С0М1');
InitCOM(l, 28800, 3);
While not KeyPressed do begin
Write (* Введите байт для посылки: *) ; ReaclLn (В); WriteData(В, 2000);
Encl;
While KeyPressed do
ReadKey;
End.
Для демонстрации преимуществ прямого программирования перед сервисами BIOS мы установили скорость обмена 28 800 бит/с. В первой консоли мы запустим передатчик и введем значение байта для передачи, а во второй консоли запустим программу чтения. Переданный байт должен отобразиться во второй консоли. Заметим, что скорость обмена можно смело поднять до 115 200 бит/с.
Глава 8
Использование
обработки прерываний
Прямое обращение к портам имеет лишь один недостаток ——для чтения данных необходимо в цикле опрашивать порт, не позволяя программе заниматься другими делами. Для устранения этого недостатка мы научимся обрабатывать прерывания.
Прерывания
Коммуникационный адаптер может вызывать специальное прерывание при возникновении некоторых условий. Условия вызова прерывания определяются значением порта ier (подробное описание порта можно найти в третьей части нашей книги, в гл. /9). Возможна настройка следующих причин прерываний:
口 прерывание по статусу модема (CTS, DSR, RI, RLSD);
口 прерывание по обрыву или ошибке линии;
П прерывание, когда буфер передачи пуст (завершение передачи);
口 прерывание при готовности принимаемых данных (по приему символа);
□ (в режиме FIFO) прерывание по тайм-ауту.
При возникновении прерывания его причину можно определить но значению регистра hr.
Прерывание, вызываемое коммуникационным портом, является аппаратным. В табл. 8.1 приведены номера аппаратных прерываний (IRQ) и соот- встстеующие им векторы прерываний (INT).
Таблица 8.1. Таблица прерываний для коммуникационных портов
Порт
IRQ
Вектор прерывания
СОМ1
IRQ4 (или 11)
INT ОСН
COM2
IRQ3 (или 10)
INT ОВН
COM3
1RQ4 (или 11)
INT ОСН
COM4
1RQ3 (или 10)
工NT ОВН
В нашем примере мы будем использовать прерывание по приему символа. Для обработки прерывания следует выполнить следующие действия:
Запретить маскируемые прерывания.
Запретить аппаратные прерывания нужного IRQ.
Заменить нужный вектор прерывания своим обработчиком.
Разрешить аппаратные прерывания.
Задать конфигурацию порта.
разрешить маскируемые прерывайия.
Перед завершением программы следует выполнить эти действия в "обрат- ном” порядке:
Если производится передача данных, дождаться ее завершения.
Запретить прерывания коммуникационного порта.
Вернуть старый обработчик прерывания.
Запретить маскируемые прерывания.
Запретить прерывания нужного IRQ.
Разрешить маскируемые прерывания.
Аппаратные прерывания требуют дополнительных действий для их обработки. При возникновении прерывания следует:
Убедиться, что прерывание вызвано приходом байта, а не другими причинами.
Отдать управление предыдущему обработчику прерываний.
Прочитав статус порта, убедиться, что байт был принят без ошибок.
При отсутствии ошибок считать байт с порта.
Послать уведомление контроллеру прерываний о завершении обработки аппаратного прерывания.
Вызвать са 1 iback-функцию пользователя.
Подробное описание необходимых нам портов контроллера прерываний приводится в гл. 19.
Модуль RS232mt
для работы с прерываниями
Для работы с прерываниями COM-порта можно использовать модуль, приведенный в листинге 8.1. Основные функции и принцип его работы легко понять из комментариев к коду.
Еще раз подчеркнем необходимость передачи специальных команд контроллеру прерываний, которые сообщают ему о корректном завершении обработки аппаратного прерывания. Если этого не сделать, программа, скорее всего, зависнет после второго или третьего вызова прерывания.
:Листинг 8.Т. Модуль RS232Xnt Для работы с прерываниями порта
Unit RS232Int;
INTERFACE uses crt;
{Тип для callback-функции, вызываемой при получении нового байта. } Туре
TCallBackRS = Procedure(Status : Byte; b : Byte);
ОСНОВНЫЕ ФУНКЦИИ
{Инициализация порта
{ Ccmlndex
{ Proc
—номер порта
—адрес callback-функции
или nil
{ Speed
{ ComPar
Procedure
—скорость работы порта
—настройки порта
InitRS232(Comlndex : Byte;
Speed : Longint;
Proc : TCallBackRS;
COMPar : Byte);
{Закрытие
procedure
порта, восстановление обработчиков INT} CloseRS232;
{Передачаprocedure
байта e порт }
OutByteToRS232(b:Byte);
{ОТЛАДОЧНЫЕ ФУНКЦИИ}
{Счетчик вызовов прерывания} function GerIntRSCount : Longint;
{ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ}
{Позволяет задавать cal1Ьасk-адрес вручную} procedure SetCallBackRS(Proc : TCallBackRS);
{Разрешает IRQ-прерывания} Procedure EnableIRQ;
{Запрещает IRQ-прерывания} Procedure DisablelRQ;
{Очистка порта. Просто холостое чтение} Procedure ClearRSPort;
{Задание параметров порта}
Procedure SetPortState(Speed : Longint; COMPar : Byte);
IMPLEMENTATION
Uses Dos;
Var { старый адрес Int OCh/OBH } SerOldVec: Pointer;
Const { служебные переменные } Save/s:Word=0;
工njsts:Byte=0;
Count:Word=0;
Srjp七工:Word=0;
AddrPr : Word = 0;
tmp : Word = 0;
Var
BaseAdr : Word;
EOI : Byte;
IRQ : Byte;
IRQMask : Byte;
INTVect : Byte;
Const { число входов в Int } Countlnt : Longint - 0;
Const { адрес ca1Ibacк-функции } CallBackPtr : TCallBackRS = nil;
{ Подпрограмма обработки прерываний от COM-порта } Procedure Ser_Int; interrupt;
Var В : Byte; COMStatus : Byte;
Begin
Inc (Countlnt); {счетчик вхождений в INT }
в:= о;
Asm
mov dx, BaseAdr { регистр идентификации прерываний } add dx, 2{ BaseAdr+2}
in alrdx
mov Int_Sts,al{сохраним его содержимое}
test al, 1{есть отложенные прерьявания? }
jz @Is_Int{да}
pushf{нет, передаем управление}
call SerOldVec{старому обработчику Inr}
@Is_Int:
moval,EOI{послать EOI для IRQ}
out20h,al{в 1 контроллер прерываний }
testInt_Sts r4{прерывание по приему?}
jnz@Read_Char{ла}
mov В, 1 @Read_Char:
inc В
End;
if (В <> 2) then begin
Asm
mov dx, BaseAdr { регистр состояния линии }
add dx, 5{ BaseAdr + 5}
in al, dx
and al, 1EH
mov
End;
COMStatus,al
(CCMStatusoO,
если была ошибка приема }
Asm
mov
dx, BaseAdr {
регистр данных
}
in
al, dx{
вводим символ
}
mov
В , al
End;
{Вызов callback, если она задана}
If GCallBackPtronil then CallBackPtr (COMStatus, В);
End;
End;
{Запрещение IRQ прерываний} Procedure DisablelRQ;
Begin
Asm
in
al,21h
{ IMR 1-го контроллера
прерываний
or
al, IR^dask
{ запретить прерывание
IRQ от ССМ }
out
21h,al
End;
End;
{Разрешение IRQ прерываний} Procedure EnableIRQ;
var NIRQMask : Byte;
Begin NIR^ask:= not IRQMask;
Asm
in
al,21h
{工MR 1-го контроллера прерываний }
and
al,NIRQMask
out
2Ih,al
{ разрешить прерывания от СОМ }
End;
End;
{ Возвращает базовый адрес порта с номером PortIndex } Function GetBaseAdr(Portindex : Byte) : Word;
Var LowAdr : Word;
Begin
{ вычисляем младшую часть адреса в таблице } LowAdr := (Portindex-1) *2;
{ получаем базовый адрес порта из таблицы } GetBaseAdr: = MemW[$0040:LowAdr];
End;
СМсчка норта. Холосное 釜ev«/\e тмзртв.、
Procedure ClearRSPort;
Begin
Asm
mov dx, BaseAdr { регистр данных}
inal, dx{ сбросить буфер приема }
End;
End;
{Установка скорости обмена и характеристик порта } Procedure SetPortState(Speed : Longint; COMPar : Byte); Var Freq : Word; FreqH, FreqL : Byte;
Begin
Freq:= 115200 div Speed;
FreqH: - Freq shr 8;
FreqL:= Freq and $00FF;
Asm
mov
dx, BaseAdr
{ регистр управления линией }
add
dx, 3
{ BaseAdr + 3}
in
al, dx
or
al,8Oh
{ установить бит DLAB }
out
dx, al
mov
dx, BaseAdr
mov
al, FreqL
out
dx,al{
'младший байт скорости }
inc
dx
{ BaseAdr+1 }
mov
al,FreqH
out
dx, al
{ старший байт скорости
mov dx, BaseAdr { регистр управления линией }
addadddx.dx.BaseAdr + 3BaseAdr + 3
add
add
dx.
dx.
BaseAdr + 3
BaseAdr + 3
mov al, COMPar out dx, al
mov dx, BaseAdr { регистр разрешения прерываний }
addadddx.dx.BaseAdr+1BaseAdr+1movmoval.al.разрешить прерывания по приемуразрешить прерывания по приему
add
add
dx.
dx.
BaseAdr+1
BaseAdr+1
mov
mov
al.
al.
разрешить прерывания по приему
разрешить прерывания по приему
out dx, al
End;
End;
{ Инициализация COM-порта }
Procedure InitRS232(Comlndex : Byte; Proc : TCallBackRS;
Speed : Longint; COMPar : Byte);
Begin
CallBackPtr:= Proc; {сохранить адрес callback}
{Получить базовый адрес порта}
BaseAdr:= GetBaseAdr(Comlndex);
{Порт не найден — выход}
If BaseAdr = 0 then Exit;
{Инициализация переменных, зависящих от номера СОМ} Case Comlndex of {Номер IRQ порта}
1:工RQ:= 4; {для CCM1/IRQ4}
2:工RQ:= 3; {для COM2/IRQ3}
End;
Case IRQ of {Номер INT вектора прерываний }
INTVect:= $0B; {для IRQ3}
INTVect:= $0C; {для 工RQ4}
End;
Case IRQ of {Сигнал EOI контроллеру прерываний}
EOI:= $63; {для IRQ3}
EOI:= $64; {для IRQ4}
End;
Case IRQ of {Сигнал EOI контроллеру прерываний}
IRQ4ask:= $08; {для IRQ3} 4:工RQMask:= $10; {для 工RQ4}
End;
Asm cli { запретить прерывания }
End;
{заггоетить IRQ прерывання } Di sable IRQ;
{Получить текущий обработчик 工NT} GetlntVec(INTVect, SerOldVec);
'{Установить новый обработчик INT} SetlntVec (INTVect, Addr (Ser_Int));
{Paзрешить IRQ прерывания} EnablelRQ;
{Задание скорости обмена и характеристик порта} SetPortState (Speed, CC^Par);
Asm
sti { разрешить прерывания } End;
{Чистка порта} ClearRSPort;
End;
{ Закрытие порта. Восстановление обработчиков INT } Procedure CloseRS232;
Begin
Asm @Wait_Free:
mov dx, BaseAdr { регистр состояния линии }
add
dx, 5
(BaseAdr+5}
in
al, dx
nop
nop
test
al,60h{
передача окончена?}
jz
@Wait_Free {
ждем, если нет}
mov
dx, BaseAdr{
регистр разрешения прерываний }
add
dx, 1{
BaseAdr+1}
mov
al, 0{
запретить прерывания}
out
dx, al
nop
nop
nop
nop
mov
dx, BaseAdr
{ регистр управления молбмом }
add
dx,4
{ BaseAdr+4}
mov
al,00000011b
{ активизировать DTR и RTS }
out
dx, al
nop
nop
nop
nop
End;
{Вернуть старый обработчик 工NT} SetlntVec(INTVect, SerOldVec);
Asm
cli{ запрет прерываний }
in al,21h{ читать маску прерываний }
nop nop
or al,IRQMask { запретить IRQ прерывания } out 21h,al
sti{ разрешение прерываний }
End;
End;
{ Подпрограмма вывода символа AL в порт.}
{ При ошибке возвращает CF=1, иначе CF=0.}
Procedure OutByteToRS232 (b:Byte) ; assembler; Asm @Wait_Line:
mov dx, BaseAdr { регистр состояния линии } add dx, 5{ BaseAdr+5}
in al, dx test al,20h{ стык готов к передаче? }
jnz @Output { да } nop loop @Wait__Line { нет, ждегд } ret
^Output:
mov al, В
mov dx, BaseAdr { регистр данных } out dx, al{ вывести символ }
nop
End;
{Доступ к счетчику вхождений в 工NT }
function Ge 11 nt RS Count : Long int;
begin
Get IntRSCount:— Countlnt;
end;
(Установка callback вне функции инициализации} procedure SetCallBackRS(Proc : TCallBackRS); begin
CallBackPtr: = Proc;
end;
END.
Для инициализации порта используется функция initRS232, параметрами которой являются номер порта, скорость обмена, настройки порта, и новый
параметр — адрес callback-процедуры, которая будет вызвана при возникновении прерывания.
Функция, адрес которой передается в качестве последнего параметра, должна иметь два основных свойства. Во-первых, параметры этой функции должны строго соответствовать параметрам TCaiiBackRS, а, во-вторых, эта функция должна иметь спецификатор far. Пример работы с портом с помощью этого модуля мы приведем в следующем разделе.
Последовательный обмен с помощью прерываний
Благодаря модулю RS232int работа с прерываниями порта очень проста. Достаточно вызвать функцию инициализации, передав указатель на callback- функцию (обратите внимание на спецификатор far) и параметры работы порта. Все остальное будет выполнено автоматически.
Перед завершением программы необходимо вызвать функцию cioseRS232, которая восстановит оригинальный обработчик прерывания.
Листинг 8.2 показывает пример чтения данных с использованием модуля RS232lnt.
Листинг 8.2. Чтение данных с помощью обработки гфер^шанйя
Uses Crt, Dos, Rs232Int;
{ Эта процедура будет вызвана при получении байта }
{ Спецификатор far после процедуры обязателен, так } { как эта процедура будет вызываться из прерывания } procedure COMCallBack(Status : Byte; b : byte); far; begin
{пока просто печатаем принятый байт}
{не рекомендуется так делать в реальной программе! } WriteLn(Ь);
End;
Begin
{Инициализация порта и регистрация са1Ibaс к-процедуры} InitRS232(1, COMCallBack, 9600, 3);
{Холостой цикл. Здесь можно делать любые действия! }
{Благодаря обработке прернвани丘 не требуется постоянного опроса порта.} While (True) do begin
If KeyPressed then { выход по нажатию ESC } If ReadKey = #27 then Break;
End;
{восстановление векторов прерываний, закрытие порта} CloseRS232;
End.
Несколько замечаний. Во-первых, из кода видно, что в основном цикле не производится никаких действий по обращению к порту. Это и есть основное преимущество прерываний: программа может заниматься своими делами, тогда как процедура обработки прерываний будет вызываться по мере необходимости.
Во-вторых, в нашем примере мы производим печать данных из callback- процедуры. В реальной программе так делать не рекомендуется. Дело в том, что аппаратные прерывания (а именно такими являются IRQ3/4) блокируют возникновение других прерываний с более низким приоритетом, а, следовательно, надо стремиться как можно быстрее завершить обработку прерывания. Функции печати слишком медленные для их использования в обработчике прерывания. Оптимально будет либо сразу обработать полученные данные, либо сохранить их в некотором буфере и обработать в основном цикле программы.
Программа передачи данных не требует обработки прерываний, но для полноты мы приведем модуль передачи данных, использующий RS232int (листинг 8.3).
:二…'\7二…?'"…
:Листинг 83 Передача данных с использованием RS232int
Uses Crt, Dos, Rs232Int;
Begin
{callback нам не нужнаf передаем nil вторым параметром} InitRS232(2, nil, 9600, 3);
While (True) do begin
If KeyPressed then {если нажата какая-нибудь клавиша...}
Case ReadKey of {какая клавиша нажата?}
* 11: OutByteToRS232($31); { послать символ <1>}
*2*: OutByteToRS232($32); { послать символ <2>}
#27: Break; { выход по ESC }
End;
End;
{восстановление векторов прерываний, закрытие порта} CloseRS232;
End.
Глава 9
Переход в Windows
Итак, наш следующий шаг — переход в Windows. Windows 95/98 хотя и не приветствует прямое обращение к портам, все же допускает его. В этой главе мы создадим специальный класс (компонент) для Delphi, позволяющий ■нам работать с поеледовательными портами почти так же, как в DOS. Более того, специальный модуль, работающий с драйвером GivelO, позволит нам осуществить прямое обращение к портам даже в Windows NT/2000. Мы расскажем о принципах работы этого драйвера и расширим его функциональность: сделаем драйвер GivelOEx, который позволит запустить любую программу с полным доступом к портам.
Переходим из DOS в Windows
В Delphi, начиная уже со второй версии, нет оператора port и работать с портами так же как в DOS не получится. Но минимизировать изменения мы постараемся.
В листинге 9.1 приводится специальный класс TPort, который позволяет применять лишь немного отличающийся метод доступа к портам.
Этот класс предоставляет достаточно простой метод переноса программ из DOS в Windows. В разд. 9.3 мы приведем пример, который специально создан как можно более похожим на наш пример из DOS-части.
yr**.e»,0""*"Wf ♦"-*-"■■♦"■**■■■■■.■*.*,一♦■■■*."-1 如如*如 W,如a♦*•**■ ■"■♦■■一*-■如*g,\ 類c如"■'"""如3".,*—-空*-,一::■",""—"如£F£飞,如如*"2*2"一"*,a*一♦■.■*如如.如如~"*如h::如
Листинг 9.1. Класс TPort, обеспечивающий доступ к портам
unit Port;
interface
uses
Windows, SysUtils, Classes;
type
TPort = class(TCowponent) private
procedure procedure procedure function function function
public
{свойство
SetByte(Index:Word;Value:Byte);
SetWord(Index:Word;Value:Word);
SetLong(Index:Word;Value:LongWord);
GetByte(Index:Word):Byte;
GetWord(Index:Word):Word;
GetLong(Index:Word):LongWord;
Для
обращения к байтовым портам }
property Byte
[Index:Word] : Byte
read GetByte
write SetByte; default;
{свойство дляproperty Word
обращения к двухбайтовьзм портам }
[Index:Word] : Word
read GetWord
{свойство для
write SetWord; обращения к четырехбайтовым портам }
property DWord[Index:Word] : LongWord read GetLong write SetLong;
end;
procedure Register;
implementation
{Регистрация компонента в палитре компонентов} procedure Register;
begin
RegisterComponents(* System*, [TPort]);
end;
{Чтение байта из порта}
function TPort.GetByte(Index: Word): Byte; begin asm
mov dx. Index in al,dx
mov Result,al
end;
end;
{Чтение слова из порта}
function TPort. Ge two rd (Index: Word) : Word;
begin asm
jtov dx. Index in ax, dx mov Result, ax end;
end;
(Чтение двойного слова из порта}
function TPort.GetLong(Index: Word): LongWord; begin
asm
mov dx,Index in eax,dx
mov Result, eax end;
end;
{Запись байта в порт}
procedure TPort.SetByte(Index: Word; Value: Byte); begin
asm
mov dx, index
mov al,Value out dx, al
end;
end;
(Запись слова в порт}
procedure TPort. SetWord (Index, Value: Word);
begin asm
mov dx, Index
mov ax, Value out dx, ax end;
end;
{Запись двойного слова в порт}
procedure TPort.SetLong(Index: Word; Value: LongWord); begin asm
mov dx. Index
mov eax,Value
out dx,eax
end;
end;
end.
Для использования этого компонента его необходимо предварительно установить, выбрав в меню Component пункт Install Component (для Delphi версии 6). После этого надо выбрать компонент TPort на вкладке System и добавить его на форму. По умолчанию новый компонент получит имя Porti. При необходимости вы можете заменить его на более понятное, например, просто Port. Обращение к портам с помощью компонента TPort выглядит лишь немного сложнее, чем в DOS (листинг 9-2).
Листинг 9.2. Обращение к портам с помощь го компонента TPort
Port.Byte[$111]:= $80;{обращение к байтовым портам}
Port.Word[$111]:= $FFFF;{обращение к двухбайтовым портам}
Port.DWord[$111]:= $1111FFFF; {обращение к четырехбайтовым портам}
{Благодаря спецификатору default к байтам можно обращаться} {без имени свойства}
Port[$111]:= $80;
A:- Port[$111];
Можно использовать этот модуль и без установки компонента, например, как показано в листинге 93
Листинг ЯЗ. Обращение к портам с помощью динамического создания компонента TPort
Uses Port;
procedure TForml.ButtonlClick(Sender: TObject); begin
(Оператор with позволит обойтись} (без промежуточной переменной} With TPort.Create(Self) do Try
Editl.Text:= IntToSt r(Byte[$ 3 F8]);
Finally
Free;
End;
end;
Первая программа для Windows
После того как мы научились обращаться к портам, попробуем создать аналог программ приема и передачи байтов из г/. 7, но в среде Windows. Мы постараемся создать как можно более похожую программу, чтобы продемонстрировать простоту перехода из DOS в Windows. А в следующих главах мы займемся использованием мродных" функций Windows.
Запустим Delphi и создадим новый проект. По умолчанию проект будет называться Project 1 и содержать модуль uniti.pas.
Добавим в проект наш модуль Port для получения доступа к портам. Единственное существенное отличие от DOS-программы, которое мы позволим себе в Windows, будет заключаться в визуальной настройке параметров порта. Если в DOS мы задавали характеристики порта числом, передаваемым в функцию init (см. примеры в z?. 7), то в Windows мы будем визуально выбирать нужные нам характеристики, а это “пело” вычислять в программе.
Весь визуальный интерфейс будет заключаться в одной форме, показанной на рис. 9.1.
Основное окно программы разделено на три части. В первой части находятся переключатели номера порта и режимов порта, во второй части ——функциональность опроса порта, а в третьей ——функциональность передачи данных.
Запустим программу (ее полный код приведен в листинге 9.4) два раза. В первой копии программы выберем порт СОМ1, а ею второй — COM2. Необходимо установить переключатели характеристик таким образом, чтобы настройки обоих портов совпадали. В одном из окон (например, в COM2) нажмем кнопку Опрос, а в другом — введем число, например, 44, и нажмем кнопку Отправить. В окне порта COM I мы должны увидеть полученный байт. Если этого не произошло, проверьте правильность распайки кабеля и соединение портов.
Рис. 9.1. Главное окно нашей первой программы чтения-записи СОМ-по рта для Windows
Еще раз заметим, что приведенный метод работы может использоваться только в Windows 95/98/МЕ. Для Windows NT/2000/XP должны использоваться либо "родные'1 функции Windows, либо драйвер прямого доступа, который мы рассмотрим в следующем разделе.
Важно!
Во всех программах для Delphi мы не приводим код файлов проекта (dpr), т. к. он ничем не отличается от создаваемого по умолчанию. Кроме того, мы не приводим текст dfrn-модулей, т. к. сомневаемся, что найдутся желающие набирать его ис листа*'. Полный, компилируемый код всех примеров есть на прилагаемом компакт-диске.
Листинг 9.4. Прямой доступ к портам из Windows-программы
program Comtest;
uses
Forms,
Unitl in PNm.PAS' {FonrJ);
{$R *.RES}
begin
Application.CreateForm(TForml, Forml);
^plication. Run;
end.
unit Unitl;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrIs, StdCtrls, Buttonsr Spin, Mask, ToolEdit, Port;
type
TFoml = class (TFom)
Panel1: TPanel;
Pane12: TPanel;
btnClose: TBitBtn;
ReadTimer: TTimer;
ParamPanel: TPanel;
rgPortIndex: TRadioGroup;
btnlnit: TButton;
rgDataLen: TRadioGroup;
rgStopBits: TRadioGroup;
rgEnableOdd: TRadioGroup;
rgBaud: TRadioGroup;
rgModeOdd: TRadioGroup;
eOtherBaud: TEdit;
GroupBox1: TGroupBox;
bRead: TSpeedButton;
Panel3: TPanel;
Bevel2: TBevel;
DR: TShape;
Label3: TLabel;
Label4: TLabel;
Labe15: TLabel;
Label6: ТLabel;
Label?: ТLabel;
Label9: TLabel;
Label10: TLabel;
Label8: TLabel;
Labelll: TLabel;
OE: TShape; PE: TShape; FE: TShape; BD: TShape; DCD: TShape; RI: TShape; CTS: TShape; DSR: TShape; TERI: TShape; Label12: TLabel;
eReadBuffer: TEdit; btnClearReadBuf: TSpeedButton; GroupBox2: TGroupBox;
btnSend: TSpeedButton; seSendByte: TSpinEdit;
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedureprocedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedureFormCreate(Sender: TObject); btnCloseClick(Sender: TObject); rgBaudClick(Sender: TObject); bReadClick(Sender: TObject); ReadTimerTimer(Sender: TObject); FormDestroy(Sender: TObject); btnSendClick(Sender: TObject); btnlnitClick(Sender: TObject); rgPortlndexClick(Sender: TObject); rgPortPropChange(Sender: TObject); eOtherBaudKeyDown(Sender: TObject; var Key: Word;
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure
Shift: TShiftState);
procedure btnClearReadBufClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
private
{ Компонент для доступа к порту }
{ Будем создавать его динамически }
FPort : TPort;
{ базовый адрес порта } BaseCOM : Word;
public
procedure ReadCOMPa ram;
end;
var
Forml: TForml;
implementation
{$R *.DFM}
{ Создание компонента доступа к портам при создании формы } procedure TForml. FormCreate (Sender: TCbject);
begin
FPort: = TPort.Create(Self);
end;
{Вызывается при унит1тожении формы (выходе из програьФлы) } procedure TForml. FormDestroy (Sender: TObject);
begin
FPort.Free;
end;
(первое чтение}
procedure TForml. FormActivate (Sender: TObject ;
begin
rgPortlndexClick(nil);
end;
{Инициализация порта}
procedure TForml.btnlnitClick (Sender: TObject);
Var В : Byte; Speed, Freq : Word;
begin
Try
{Блокируем повторное нажатие кнопки или}
{изменение параметров, пока не установим}
{текущие параметры}
РаramPane1.Enabled:= False;
ReadTimer.Enabled := False;
{Вычисляем и программируем делитель скорости}
If (rgBaud.Itemlndex = (rgBaud.Iterns.Count 1)) then {скорость обмена не из списка} Speed:= StrToIntDef(eOtherBaud.Text, 0)
Else {скорость обмена из списка}
Speed:= StrToIntDef(rgBaud.Items[rgBaud.Itemindex], 0);
If Speed = 0 then begin
MessageDlg(*Плохое значение скорости обмена 1 *, mtError, [mbOK], 0);
Exit;
End;
{Устанавливаем бит DLAB для программирован%月 делителя} FPort[BaseCOM+3]:= FPort[BaseCOM+3] or $80;
{Вычисляем делитель} Freq:= 115200 div Speed;
{Программируем делитель}
FPort[BaseCOM+0]:= Lo(Freq);
FPort[BaseCOM+1]:= Hi(Freq);
{Сбрасываем бит DLAB} FPort[BaseCOM+3]:= FPort[BaseCOM+3] and $7F;
B:= 0;
Case rgModeOdd.Itemlndex of
0: // нечет B:= В or $00;
// чет B:= В or $10;
// постоянная 1 B:= В or $20;
// постоянный 0 B:= В or $30;
End;
Case rgEnableOdd.Itemindex of 0: // контроль четности запрещен
В:= В or $00;
// контроль четности включен В:== В or $08;
End;
Case rgStopBits. Itemlndex of
0: // 1 стоп-бит
B:= В or $00;
// 2 (1.5) стоп-бита
В:= В or $04;
End;
Case rgDataLen. Itemlndex of
0: // 5 бит
B:= В or $00;
// 6 бит
B:= В or $01;
// 7 бит
B:= В or $02;
// 8 бит
B:= В or $03;
End;
{установить новые характеристики порта} FPort[BaseCOM+3] := В;
Finally
ParamPanel. Enabled: = True;
ReadTimer. Enabled : = bRead. Down;
End;
{Перечитать настройки порта}
ReadCCMPa ram;
end;
(Перечитать настройки порта при выборе порта} procedure TForml.rgPortlndexClick(Sender: TObject); begin
{Базовый адрес порта}
Case rg Port Index. Itemlndex of
0: BaseCOM:= $3F8;
1: BaseCOM:= $2F8;
End;
ReadCOMParam;
end;
{Чтение текущих настроек порта} procedure TForml.ReadCCMParam;
Var В : Byte; Speed, Freq : Word; FreqL, FreqH : Byte;
: Integer;
begin
Try
{Блокируем изменение параметров на время чтения настроек} ParamPanel.Enabled:= False;
ReadTimer.Enabled := False;
{Устанавливаем бит DLAB для чтения делителя} FPort[BaseCOM+3]:= FPort[BaseCOM+3] or $80; FreqL:= FPort[BaseCOM+O];
FreqH:= FPort[BaseCOM+1];
{Сбрасываем бит DLAB } FPort [BaseCOM+3] : = FPort [BaseCOl+3] and $7F;
{Вычисляем делитель} Freq:= FreqH shr 8 + FreqL;
{Вычисляем скорость обмена} If Freq > 0 then Speed:= 115200 div Freq else Speed:= 0;
{Проверяем наличие такой скорости в таблице}
i:= rgBaud.Items.IndexOf(IntToStr(Speed));
If i < 0 then begin {В таблице не нашли — пишем '*другая"} rgBaud.Itemindex:= rgBaud.Items.Count-1;
eOtherBaud.Text := IntToStr(Speed); eOtherBaud.Visible:= True;
End Else begin
rgBaud.Itemlndex:= i; eOtherBaud.Visible:= False;
End;
В: = FPort[BaseCOM+3]; {характеристики порта}
Сазе (В and $30) of $00: // нечет
rgModeOdd.Itemlndex:= 0; $10: // чет
rgModeOdd.Itemlndex:= 1; $20: // постоянная 1
r^IodeOdd. I temlndex := 2;
$30: // постоянный 0
rgModeOdd.Itemlndex:= 3;
End;
Case (B and $08) of
$00: // контроль четности запрещен rgEnableOdd.Iteralndex:= 0;
$08: // контроль четности включен rgEnableOdd.Itemlndex:= 1;
End;
Case (B and $04) of
$00: // 1 стоп-бит rgStopBits.Itemindex:= 0;
$04: // 2 (1.5) стоп-бита rgStopB its. I teml ndex: = 1;
End;
Case (B and $03) of
$00: // 5 бит rgDataLen.Itemlndex:= 0;
$01: // 6 бит
rgDataLen. Itemlndex : = 1;
$02: // 7 бит
rgDataLen. Itemlndex:= 2;
$03: //8 бит
rgDataLen.Itemindex:= 3;
End;
Finally
ParamPanel. Enabled: = True;
ReadTimeг.ЕпаЫed := bRead,Down;
End;
End;
{Кнопка закрытия программы}
procedure TForml.btnCloseClick(Sender: TObject);
begin
Close;
end;
procedure TForml.rgBaudClick(Sender: TObject);
begin
{Видимость окошка "Другая скорость**}
eOtherBaud.Visible:=
(rgBaud.Itemindex = (rgBaud.Items.Count-1));
{Изменение характеристик порта}
rgPortPropchange(nil);
end;
procedure TForml.eOtherBaudKeyDown{Sender: TObject; var Key: Word; Shift: TShiftState);
begin
{Изменение скорости обмена по клавише Enter}
If Key = 13 then rgPortPropChange(nil);
end;
{Любое изменение характеристик порта}
procedure TForml.rgPortPropChange(Sender: TObject); begin
{Переинициализация}
btnlnitClick(nil);
end;
{Кнопка опроса порта}
procedure TForml.bReadClick(Sender: TObject);
begin
ReadTimer.Enabled:= bRead.Down;
end;
Const {Цвета индикаторов}
IColor : Array [Boolean] of TColor = (clWhite, clGreen);
procedure TForml. ReadTimerTimer (Sender: TObj ect);
Var B, Bl, B2 : Byte; S : String; begin
{Состояние модема % порта считываем до чтения байта, }
{т. к. после чтения данных с порта состояние порта изменится.}
Bl:= FPort[BaseCOM+5]; {Байт состояния порта}
В2:= FPort[BaseCOM+б]; {Байт состояния модема}
{Проверка наличия данных и их отображение}
If (Bl and $01) <> 0 then begin {Бит "есть данные для чтения"} В:= FPort[BaseCOM+O]; { принятый байт }
S:= eReadBuf fer.Text;
S:= S + Format('$話X', [В]);
If Length(S) > 50 then Delete(S, 1, 3); eReadBuffer.Text:= S;
End;
{Отображение индикаторов состояния порта}
{Сигнал DR (данные готовы) }
DR.Brush.Color:= IColor[(Bl and $01) <> 0]; {Сигнал OE (переполнение) }
0E.Brush.Color:= IColor[(Bl and $02) <> 0]; {Ошибка паритета}
PE.Brush.Color:^ IColor[(Bl and $04) <> 0]; {Ошибка кадра (неверный стоп-бит)}
FE.Brush.Color:- IColor[(Bl and $08) <> 0];
(Индикатор обрыва линии}
BD.Brush.Color:= IColor[(Bl and $10) <> 0];
(Отображение индикаторов состояния модема} {Состояние линии DCD (data carrier detect) } DCD.Brush.Color := IColor[(B2 and $80) <> 0]; (Состояние линии RI (ring indicator)}
RI .Brush.Color := IColor[(B2 and $40) <> 0]; {Состояние линии DSR (data set ready) }
DSR.Brush.Color := IColor[(B2 and $20) <> 0];
{Состояние линии CTS (clear to send)}
CTS.Brush.Color := IColor[(B2 and $10) <> 0];
{Сигнал "спад огибающей" (окончание звонка)}
TERI.Brush.Color:= IColor[(B2 and $04) о 0];
end;
{Очистить буфер чтения}
procedure TForml.btnClearReadBufClick(Sender: TObject);
begin
eReadBuffer.Text:= * *;
end;
{Отправка байта}
procedure TE'orml.btnSendClick (Sender : TObject);
begin
ETort[BaseCOM]:= seSendByte.Value;
end;
end.
Интересно понаблюдать за индикаторами ошибок связи. Для эксперимента можно установить разные характеристики портов приемника и передатчика и пробовать передать байт. Варьируя характеристики, можно добиться индицирован ия ошибок переполнения или четности.
Перенос программ из Windows 9х в Windows NT/2000
Запустив пример из предыдущего раздела в Windows 98, мы увидим в Editi значение порта $3F8. а запустив его в Windows 2000 — сообщение об ошибке при попытке прямого чтения порта:
"Project Project1.exe raised except ion class EPrivilege with message * Privileged instruction'. Process stopped. Use Seep or Run to continue.n
Сообщение об ошибке вызвано тем, что версии Windows NT и вьпие блокируют прямые обращения к аппаратным ресурсам компьютера. Для использования прямого обращения к портам в Windows NT/2000/XP следует использовать специальный драйвер. Таким образом, Windows отделяет уровень пользовательских программ от аппаратного уровня, что существенно повышает надежность и защищенность операционной системы: обычные про-
граммы обязаны обращаться к драйверу, а драйвер блокирует одновременное использование аппаратных ресурсов, если программа не сообщит ему о возможности их разделения между несколькими программами. Именно так делают стандартные функции Windows для последовательных портов, которые мы рассмотрим в гл. 10.
Но что делать, если программа уже написана и работает, а времени на переделку нет? Другими словами, как перенести программу, работающую на- прямую с аппаратурой, из Windows 95/98 в Windows NT/2000?
Мы будем рассматривать два варианта перехода из Windows 95/98 в Windows NT/2000/XP. Первый вариант, когда нам доступны исходные тексты программы и есть возможность ее перекомпилировать (т. е. внедрить в программу новый код), м второй, когда исходный текст отсутствует, а заставить программу работать в новых версиях Windows все равно нужно. Вообще говоря, оба этих случая сводятся к одному решению ——написанию специа-пьного драйвера, разрешающего доступ к портам. Только в первом случае он будет разрешать доступ для текущего процесса, а во втором ——для чужого.
За основу мы возьмем драйвер GivelO, ставший уже классическим. Правда, он рассчитан для работы только в первом случае, когда необходимо разрешить доступ своему процессу, но небольшая его модификация позволит нам решить и вторую проблему.
Драйвер GivelO создан Дейлом Робертсом (Dale Roberts) специально для обхода ограничения прямого доступа к портам. Исходный код драйвера и некоторые объяснения его работы приведены в пр ил. 7. Конечно, в рамках нашей книги мы не можем приводить подробные объяснения принципов работы и написания драйверов. Мы ограничимся чисто практическим подходом.
Получение доступа к портам в Windows 2000/ХР
Читателей, интересующихся принципом работы драйвера, мы отсылаем к прил. % где описан принцип работы драйвера и приводится его исходный код. Использовать этот драйвера очень просто — достаточно загрузить его в память. После этого его можно даже выгрузить, для работы программы он не требуется.
Для упрощения использования драйвера мы напишем специальный модуль Giveio.Pas (листинг 9.5). Класс, описанный в этом листинге, содержит пять методов:
П Createservice — регистрация драйвера в реестре (создание записи о сервисе);
口 startservice — старт сервиса, соответствующего драйверу;
GiveioForCurrentProcess — получение доступа для текущего процесса;
口 stopservice ——останов сервиса драйвера;
Removeservice — удаление записи о драйвере из реестра.
Мы привели список методов именно в том порядке, в котором их нужно вызвать для получения полного доступа к портам. Причем, как мы уже говорили, совершенно не важно, будет ли драйвер выгружен сразу же после вызова GiveioForCurrentProcess или при завершении программы.
Код программы, показывающей работу драйвера, мы приведем чуть позже, объединив его с примером предоставления доступа без перекомпиляции программы.
Листинг 9.5. Исходный код модуля Gxveio. Fas
>.:!.... J.2^:f..::..^^二.!..:“.匚.*”,„.;“『•...<:,;• :.・..
unit GivelO;
interface
Uses Windows, SysUtils;
type
TGivelO = class
Public
Function CreateService(SysPath : String) : Boolean;
Function RemoveService : Boolean;
Function StartService : Boolean;
Function StopService : Boolean;
Procedure GiveioForCurrentProcess;
End;
inplementation
Uses WinSvc, Dialogs;
Const
DriverName : PChar= *giveio*#0;
FileDriver : String = *giveio.sys*#0;
Function TGivelO.CreateService(SysPath : String) : Boolean; var IpServiceArgVectors : PChar;
hSCMan, hService: SC_HANDLE;
DriverPath : String;
Begin
Result := False;
{= Создание сервиса ==)
{Сервис регистрируется в ветке реестра}
{HKEY_L(X/\L_MACHINE \ SYSTEM\Cur rentCont rolSet \ Services\giveio}
{Будут созданы ключи: }
{ DisplayName = имя_драйвера (равно giveio)}
{ ImageРаth = путь к файлу sys. При регистрации в локальной директории
{будет равно \??\Е:\Test\giveio.sys}
hSCMan: = WinSvc.OpensCManager(
Nil,{ local }
Nil,{ SERVICES_ACTIVE_DATABASE }
SC_MANAGER_ALL_ACCESS
);
If hSCMan = 0 then Exit;
{Получаем полное имя к файлу драйвера }
DriverPath := Sys Path + FileDriver;
{Создаем сервис (регистрируем драйвер} }
hService:= WinSvc.CreateService (hSCMan, Drivername, DriverName, SERVICE_ALLJkCCESS, SERVICE_KERNEL_DRIVER, SERV 工 CE_DEMAND_START, SERVICE_ERROR_NORMAL,
PChar(@DriverPath[1]), nil,nil, nil,nil,nil);
{Файл не найден или сервис уже зарегистрирован}
If hService = 0 then begin
MessageDlg (IntToStr (GetLastError) , mtError, [mbOK], 0);
CloseServiceHandle {hSCMan);
Exit;
End;
{Регистрация успешна}
WinSvc.CloseServiceHandle (hService);
WinSvc.CloseServiceHandle(hSCMan);
Result:= True;
End;
Function TGivelO.RemoveService : Boolean;
var servicestatus : TServiceStatus;
hSCMan, hService : SC_HANDLE;
Begin
Result:= False;
{== Удаление сервиса из реестра}
hSCMan:= WinSvc.OpenSCManager(Nil,Nil,SC_MANAGER_ALL_ACCESS);
If hSCMan = 0 then Exit;
hService:= WinSvc.OpenService(hSCMan, DriverName, SERV工CE_ALL_ACCESS); {Ошибка открытия сервиса}
If hService=O then begin
CloseServiceHandle(hSCMan);
Exit;
End;
WinSvc.DeleteService(hService);
WinSvc.CloseServiceHandle(hService);
WinSvc.CloseServiceHandle(hSCMan);
Result:= True;
End;
Function TGivelO.Startservice : Boolean;
var IpServiceArgVectors : PChar;
hSCMan, hService : SC_HANDLE;
DriverPath : String;
Begin
Result:= False;
{== Старт зарегистрированного сервиса} hSCMan := WinSvc.OpenSCManager(Nil, Nil, SC_MANAGER_CONNECT); If hSCMan = 0 then Exit;
hService:= WinSvc.OpenService(hSCMan, DriverName, SERVICE_START); If hService = 0 then begin
CloseServiceHandle (hSCMan);
Exit;
End;
IpServiceArgVectors: =nil;
WinSvc.Startservice(hService, 0, IpServiceArgVectors);
WinSvc. CloseServiceHandle (hService);
WinSvc. CloseServiceHandle (hSCMan);
Result: = True;
End;
Function TGivelO. StopService: Boolean;
var servicestatus : TServiceStatus; hSCMan, hService : SC_HANDLE;
Begin
Result := False;
{== Остановка сервиса}
hSCMan:= WinSvc.OpenSCManager(Nil, Nil, SC_MANAGER_CONNECT); If hSCMan = 0 then Exit;
hService:= WinSvc.OpenService(hSCMan, DriverName, SERVICE_STOP);
If hService = 0 then begin
WinSvc. CloseServiceHandle (hSCMan);
Exit;
End;
WinSvc.Controlservice(hService, SERVICE_CONTROL_STOP, servicestatus); WinSvc. CloseServiceHandle(hService);
WinSvc.CloseServiceHandle (hSCMan);
Result: =- True;
End;
{== Загрузка драйвера в память —}
{Дает доступ к портам для текущего процесса} Procedure TGivelO.GiveloForCurrentProcess; var hDevice : SC_HANDLE;
Begin
hDevice:= CreateFile('\\.\giveio’,
GENERIC_READ or GENERIC_WRITEZ
О,nil,
OPEN—EX 工 ST 工 NG,
FILE_ATTRIBUTE_NORMAL, О
);
CloseHandle(hDevice);
End;
End.
Расширение возможностей GivelO
Итак, наша задача: доработать драйвер GivelO таким образом, чтобы его можно было использовать для получения доступа к портам не только текущего процесса, но и любого процесса, идентификатор которого нам известен.
Прежде чем приступить к работе, следует рассмотреть, как прикладная программа взаимодействует с драйвером. В az. 13 мы еще будем обсуждать функцию Devi се I oCont roi, а пока только опишем параметры этой функции:
hDevice — дескриптор драйвера, получаемый с помощью вызова CreateFiie. В качестве открываемого файла используется имя драйвера;
код команды, состоящий из идентификатора драйвера, атрибутов команды и номера команды. Идентификатор драйвера гарантирует доставку команды только одному, нужному драйверу, а номер команды передает указание на выполнение конкретных действий;
входной буфер и его размер ——передают данные, необходимые драйверу для выполнения указанной команды;
выходной буфер и его размер — передают драйверу буфер для размещения ответа;
口 код результата —— позволяет узнать правильность выполнения команды.
Этих сведений нам пока достаточно. Итак, прикладная программа вызывает DeviceioControi с кодом функции п некоторыми параметрами. Ядро Windows ищет соответствующий драйвер (именно для этой цели, чтобы система могла найти драйвер, мы заре гистр и ровал и его в реестре, вызвав CreateService) и передает код функции драйверу. Драйвер выполняет указанные действия и, при необходи мости, возвращает некоторые данные. Весь интерес заключается в том, что функции драйвера выполняются на уровне ядра системы и им доступны все ресурсы Windows. Заметим, что "правильный'1 драйвер устройства должен реализовывать все необходимые функции доступа к аппаратуре внутри себя. Другими словами, надо сделать обработку некоторого набора кодов функций, а программа должна ими пользе-
ваться (например, коды драйвера последовательного порта описаны в 乙?. 22). У нас же другая цель. Мы хотим создать одну функцию, выполняющуюся на уровне драйвера. Эта функция должна уметь открывать доступ указанному процессу.
Таким образом, нам надо реализовать две части обработки. Первая часть —— обработка кода команды в драйвере, вторая ——передача данных из программы в драйвер.
Для начала нам нужно придумать номер команды. Вообще говоря, это произвольное положительное число от $800 до $FFF. Самое главное, чтобы и драйвер, и программа использовали одно и то же число. Второе число, необходимое для передачи команды,——код типа драйвера. Коды драйверов от О до 32767 зарезервированы, а коды от 3 2768 до 6553 5 могут использоваться прикладными программами. Естественно, драйвер нам придется компилировать с помощью Microsoft DDK, т. е. на языке С, а обработку (обращение) к нему мы будем производить, как обычно, на Delphi. В листинге 9.6 приводятся константы, составляющие код функции и макрос для его генерации. А листинг 9.7 содержит тот же код на Delphi.
箏…:•”•""“匸”二 т—••»—;••:■:-'■■ •*""•?•,<“••”“-
Листинг 9.6< Файл описания команды для драйвера (GivelOE)c.h)
// Номера 327 68-65535 зарезервированы для пользователя
Mefine GIVEIO_TYPE 40000
// Коды функций IOCTL от 0x800 до OxFFF могут использоваться
Mefine 工。CTL_工OPM_GET_ALL_ACCESS\
CTL_CODE (GIVEIO_TYPE, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
;Листинг 9,7. Описание команды драйвера в Delphi iN.^..“..“..;.y“G.;;:;;x.."X:i・;£*U』Ckx“““W「G・k“M““;fM;,..”、.V; ;<......«... .3.
Const
GIVEIO_TYPE = 40000;
METHOD_BUFFERED =0;
FILEJ\NY_ACCESS 二 $0000;
function Get_Ctl_Code (Nr: Integer) : Cardinal;
begin
Result:=
(GIVEIO_TYPE shl 16) or
(FILE_ANY_ACCESS shl 14) or
(Nr shl 2) or
METHOD_BUFFERED;
end;
Var
IOCTL_IOPM_GET_ALL_ACCESS: Cardinal;
IOCTL_IOPM_GET_ALL_ACCESS: = Get_Ctl_Code ($900);
Теперь остается добавить код обработки новой команды в драйвер. Как мы уже говорили, при выполнении этой команды драйвер должен открывать доступ к процессу, идентификатор которого передается как входной параметр команды. Код получившегося драйвера приведен в листинге 9.8. Для его компиляции нужно использовать утилиту Build из поставки Windows DDK, а на прилагаемом компакт-диске можно найти уже готовый SYS-файл.
Листинг 9.8. Код расширенного драйвера GivelOEx
/*
Драйвер предоставления прямого доступа для процесса режима пользователя. Драйвер основан на коде драйвера Give10 от Dale Roberts, но в него добавлена возможность предоставления доступа другим процессам с помощью команды 0x900
Компиляция: Исполь зуйте средство DDK BUILD
*/
#include <ntddk.h>
#include "Give工OEx.h"
/* Имя нашего драйвера устройства */
#define DEV工CE_NAME_STR工NG L”giveiO6x”
/*
Это пструктураи I0PM. Это просто массив байт размером 0x2000. Он содержит 8К * 8 бит = 64 Кбит IOPM, которые покрывают все 64- килобайтное адресное пространство в/в х86 процессора. Каждый нулевой бит предоставляет доступ к соответствующему порту для user-mode процесса. Каждый единичный бит запрещает доступ к в/в через соответствующий порт.
*/
#define IOPM_SIZE 0x2000
typedef UCHAR I0PM[I0PM SIZE];
/*
Это будет содержать просто массив нулей, который будет копироваться в настоящую IOPM в TSS через Ke386SetIoAccessMap () . Память вьщеляется во время загрузки драйвера.
*/
ЮРМ * 10РМ_1оса1 = 0;
Это две недокументированных функции, которые ыы используем, чтобы дать доступ к в/в вызывающему процессу. Ke386IoSetAccessMap () копирует переданную карту в/в в TSS. Ke386IoSetAccessProcess() изменяет указатель смещения ЮРМ, после чего только что скопированная карта в/в начинает использоваться. Иначе смещение ЮРМ указывает за предел сегмента TSS, что приведет к генерации исключения при попытке доступа к в/в любым user-mode процессом.
*/
void Ke386SetIoAccessMap (int, IOPM *);
void Ke386QueryIoAccessMap (int, IOPM *);
void Ke3861oSetAccessProcess(PEPROCESS, int);
NTSTATUS PsLookupProcessByProcessId(IN ULONG ulProcId,
OUT struct _EPROCESS ** pEProcess); /*
Сервисная функция драйвера.
*/
NTSTATUS GiveioDeviceControl (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
/*
Освободить все вьщеленные ранее объекты
*/
VOID GiveioUnload(IN PDRIVER_OBJECT Driverobject) {
WCHAR DOSNameBuffer[] = Ln\\DosDevices\\" DEVICE_NAME_STRING;
UNICODE_STRING uniDOSString;
if (IOPM_Local)
MmFreeNonCachedMemory(IOPM_local, sizeof(IOPM));
RtllnitUnicodeString(&uniDOSString, DOSNameBuffer);
loDeleteSymbolicLink (&uniDOSString);
loDeleteDevi ce(Drive rObj ect->DeviceObj ect);
/*
Устанавливаем IOPM (карту разрешения в/в) процесса ProcessID так, что ему предоставляется полный доступ к в/в. Наш массив 0РМ_1оса1 [] содержи? только нули, соответственно, IOPM обнулится. Если OnFlag = 1, процессу предоставляется доступ к в/в. Если он равен 0, доступ запрещается.
*/
VOID SetlOPermissionMap(PEPROCESS ProcessID, int OnFlag)
(
Ke386loSetAccessProcess(ProcessID, OnFlag);
Ke38 6SetloAccessMap(1, IOPM_local);
}
/*
Точка входа в драйвер. Выполняется при загрузке. Установка разрешения доступа ко всем портам для вызывающего процесса.
*/
void Give10(void)
{
SetlOPermissionMap(PsGetCurrentProcess(), 1);
}
/*
Служебный обработчик для user-mode вызова CreateProcess(). Эта функция введена в таблицу вызовов функций объекта драйвера с помощью DriverEntryO. Когда user-mode приложение вызывает CreateFile() , эта функция получает управление все еще в контексте вызвавшего приложения, но с CPL (текущий уровень привилегий процессора), установленным в 0. Это позволяет производить операции, возможные только в kernel mode. GivelO() вызывается для предоставления вызывающему процессу доступа к в/в. Все, что приложение режима пользователя, которому нужен доступ к в/в, должно сделать, — это открыть данное устройство, используя CreateFile(). Никаких других действий не нужно.
*/
NTSTATUS GiveioCreateDispatch(
IN PDEVICE_OBJECT Deviceobject,
IN PIRP Irp
{
GivelO(); // дать доступ текущему процессу
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
loCompleteRequest (Irp, IO_NO_INCREMENT); return STATUS_SUCCESS;
/*
Процедура входа драйвера. Эта процедура вызывается только раз после загрузки драйвера в память. Она выделяет необходимые ресурсы для работы драйвера. В нашем случае она выделяет память для массива IOPM и создает устройство, которое может открыть приложение режима пользователя. Она также создает символическую ссылку на драйвер устройства. Это позволяет user-mode приложению получить доступ к нашему драйверу, используя \\.\giveioex нотацию.
*/
NTSTATUS DriverEntry (
IN PDRIVER_OBJECT Driverobject,
IN PUNICODE__STRING RegistryPath
PDEVICE_OBJECT deviceobject;
NTSTATUS status;
WCHAR NameBuffer[] = L”\\Device\\” DEVICE_NAME_STRING;
WCHAR DOSNairieBuffer [ ] = LH \\DosDevices\\** DEVICE_NAME_STRING;
UNICODE_STRING uniNameString, uniDOSString;
// &щелим буфер для локальной IОРМ и обнулим его.
IOPM_local = MmAllocateNonCachedMemory (sizeof (ТОРИ));
if (IOPM_local = 0)
return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemorу (IOPM_loca 1, sizeof (IOPM));
// Инициализируем драйвер устройства и объект
// устройства (device object)
RtllnitUnicodeString (SuniNameString, NameBuffer);
RtllnitUnicodeString (&uniDOSString, DOSNairieBuf fer);
status = loCreateDevice (DriverObject, 0, SuniNaireString,
FILE__DEVICE_UNKNOWN, 0, FALSE, &deviceObject);
if(!NT_SUCCESS(status)) return status;
status = loCreateSymbolicLink (&tiniDOSString, &uniNameString); if (!NT_SUCCESS(status))
return status;
// Инициализируем точки входа драйвера в объекте
// драйвера. Все, что нам нужно, — это операции создания
// (Create) и выгрузки (Unload)
DriverObj ect->Maj orFunct ion[IRP_MJ_CREATE]= GiveioCreateDispatch;
Drive rObj ect->Ma j orFunct ion[IRP_MJ_DEVICE_CONTROL]=
Give ioDeviceCont rol;
DriverObject->DriverUnload = GiveioUnload;
return STATUS_SUCCESS;
}
NTSTATUS
Give ioDeviceCont rol(
IN PDEVICE_OBJECT Deviceobject, IN PIRP plrp
NTSTATUSP工O_STACK_LOCAT工ONULONGULONGPULONGNTSTATUSP工O_STACK_LOCAT工ONULONGULONGPULONGntStatus = STATUS_SUCCESS;
NTSTATUS
P工O_STACK_LOCAT工ON
ULONG
ULONG
PULONG
NTSTATUS
P工O_STACK_LOCAT工ON
ULONG
ULONG
PULONG
irpSp;
inBufLength; /* Длина входного буфера */ outBufLength; /* Длина выходного буфера */
LongBuffer; /* Указатель на входной буфер */
irpSp= IoGetCurrentIrpStackLocation( plrp );
inBufLength = irpSp->Parameters.Devi сеIoCont го1.InputBufferLength;
LongBuffer = (PULONG) p!rp->AssociatedIrp.SystemBuffer;
switch ( irpSp->Parairieters. DeviceloControl. loControlCode ) {
case IOCTL_IOPM_GET_ALL_ACCESS:
if (inBufLength >= 4) {
ULONG ProcessID = LongBuffer[0];
struct _EPROCESS *Process;
PsLookupProcessByProcessId(ProcessID, &Process);
Ke386Set工oAccessMap(1, IOPM_local);
Ke38 6IoSetAccessProcess(Process, 1);
ntStatus = STATUS_SUCCESS;
} else {
ntStatus = STATUS_BUFFER_TOO_SMALL;
pIrp->IoStatus.Information = 0; /* Output Buffer Size */
break;
} }
pIrp->IoStatus.Status - ntStatus;
loCompleteRequest ( plrp, IO_NO_INCREMENT );
return ntStatus;
}
Обработчик НОВОЙ команды находится внутри процедуры GiveioDeviceControl. При необходимости добавить другие команды, следует выполнить три действия:
Добавить новый код команды в заголовочный файл GivelOEx.h.
Добавить обработчик нового кода в оператор switch процедуры GiveioDeviceControl.
Заменить загрузку обычного giveio.sys на расширенный драйвер giveioex.sys.
Со стороны драйвера все готово. Остается только добавить новый метод в класс TGiveio, вызывающий только что созданную функцию $900, как показано в листинге 9.9.
■<M«■*"",",£"".K2,,*"■《","?,,yW*"*",■,y%f ■*•■,,.*■■■-■■■♦■•"■"""也。?,""■,♦,■■■.,■■■""■■"•■■-*""♦."...,、.."■■.♦",*",,"■",,盡*" "..".,.f?e,",':,..."*"*,,:...N"..:,YK,h""".V・
i Листинг 9.9. Функция предоставления доступа к портам выбранного процесса
l,.Л*.*йi*.;;....:.:.-一:姦上:“? ;,?.»....^^.^...^:^....^^^...^;.;;..^.I-;....^^..^!*.....;»J^.....?.^..;.*.*.^.^*-.,...!....-..-.-..^^*^...^--;..:...*
unit Give IO;
Uses Windows, SysUtils;
type
TGivelO = class
Public
Procedure GiveloForProcess(dwProcessId: Cardinal);
End;
implementation
Uses WinSvc, Dialogs;
Const
GIVEIO_TYPE二 40000;
METHOD_BUFFERED= 0;
FILE_ANY_ACCESS工 $0000;
function GeJCtl—Code (Nr: Integer) : Integer;
begin
Result:=
(GIVEIO_TYPE shl 16) or
(FILE_ANY_ACCESS shl 14) or
(Nr shl 2) or METHODJBUFFERED; end;
Procedure TGivelO.GiveloForProcess(dwProcessId : Cardinal); var hDevice : SC_HANDLE; Result : Cardinal; begin
hDevice:= CreateFile('\\.\giveioexL GENER工C_READ or GENERIC_WRITE, 0,nil,
OPEN_EX工ST工NG, FI LE_ATTRIBUTE_NORMAL, 0
DeviceloContго 1 (hDevice, Get_Ctl_Code ($900), @dwProcess工d, SizeOf(dwProcessId), nil, 0, Result, nil);
CloseHandle (hDevice);
end;
Работа с драйвером GivelOEx
Теперь все готово к проверке работоспособности нашего драйвера. В качестве тестовой программы мы создадим небольшой проект, выполняющий следующие действия:
регистрация драйвера;
П удаление драйвера;
口 предоставление доступа к портам гекущему процессу;
тестирование прямого доступа к портам;
запуск нового процесса и предоставление ему доступа к портам.
Вид формы тестового проекта показан на рис. 9.2, а код соответствующего модуля приведен в листинге 9.10.
Тест GivelOExТест GivelOEx-Драйвер доступа-Драйвер доступа区1区1
Тест GivelOEx
Тест GivelOEx
-Драйвер доступа
-Драйвер доступа
区1
区1
:Загрузить:ЗагрузитьВыгрузитьВыгрузить
:Загрузить
:Загрузить
Выгрузить
Выгрузить
Т екущий процесс
Доступ текущему процессу
Обратиться к порту $3F8
} Запуск другого процессаПуть к ехе Файлч:
I
Запустить
Рис. 9.2. Окно программы тестированиядрайвера GivelOEx
;Листинг 9.10. Модуль тестирования драйвера GivelOEx
如;*心..”;•■"■.;“:♦♦“二“*.7・“ “」■> а、・8;,*';з,,Н;и:.;.* 力““、訂,““3;託工-«““•■;;“♦:♦•:厶、•♦人 宀■■■*•;'■—“,:•”*♦;
unit Unitl;
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Fomas, Dialogs, StdCtrls, GivelO;
type
TForml = class(TForm) GroupBoxl: TGroupBox; btnLoad: TButton; btnUnload: TButton; GroupBox2: TGroupBox; btnGiveCurr: TButton;
btnTest: TButton; GroupBox3: TGroupBox; ExeName: TEdit; btnExec: TButton;
Labe11: TLabe1;
procedure procedure procedure procedure procedure procedure procedure privateFGivelO :procedure procedure procedure procedure procedure procedure procedure privateFGivelO :TGivelO;TGivelO;btnTestClick(Sender: TObject); FormCreate(Sender: TObject); FormDestroy(Sender: TObject); btnLoadClick(Sender: TObject); btnUnloadClick(Sender: TObject); btnGiveCurrClick(Sender: TObject); btnExecClick(Sender: TObject);
procedure procedure procedure procedure procedure procedure procedure private
FGivelO :
procedure procedure procedure procedure procedure procedure procedure private
FGivelO :
TGivelO;
TGivelO;
public end;
var
Forml: TForml; implementation {$R *.dfm}
{Создание компонента TGivelO}
procedure TForml.FormCreate(Sender: TObject); begin
FGivelO:= TGivelO.Create;
end;
{Уничтожение компонента TGivelO}
procedure TForml.FormDestroy (Sender: TObject);
begin
FGivelO. Free; end;
{Тестовая кнопка обращения к базовому СОМ-порту} procedure TForml.btnTestClick (Sender: TObject); var Result : Byte;
begin
asm
mov dx, $3F8
in al,dx
mov Result,al end;
end;
{Регистрация сервиса в реестре и старт}
procedure TForml.btnLoadClick(Sender: TObject);
begin
FGivelO.CreateService (ExtractF'ilePath (E?arairiStr (0))); FGivelO. Startservice;
end;
{Останов сервиса и удаление из реестра}
procedure TForml.btnUnloadClick (Sender: TObject); begin
FGivelO. StopService;
FGivelO. RemoveService;
end;
(Получить доступ к портам для текущего процесса} procedure TFoirml.btnGiveCurrClick(Sender: TObject); begin
FGivelO. GiveloForCurrentProcess;
end;
{Запустить другой процесс (программу) и предоставить} {ему доступ к портам}
procedure TForml.btnExecClick(Sender: TObject);
var si : STARTUPINFO; pi : PROCESS_INFORMATION; begin
ZeroMemory(@si, sizeof(si));
si.cb:= sizeof(si);
if not CreateProcess(nil, PChar(ExeName.Text), nil, nil. False, 0, nil, nil, si, pi) then begin
MessageDlg(•Ошибка запуска программы*, mtError, [mbOK], 0);
Exit;
End;
FGivelO.GiveloForProcess(pi.dwProcessId); end;
end.
Для проверки запустим нашу программу (не забывайте, что мы работаем в Windows 2000 или Windows ХР!) и нажмем кнопку Обратиться к порту $3F8. Если все правильно, появится окно, сообщающее, что программа попыталась выполнить запрещенную для нее инструкцию (рис. 9.3).
Рис. 9.3. Сообщение о попытке выполнить запрещенную инструкцию
Теперь нажмем кнопку Загрузить. Как видно из кода обработчика btnLoadciicko, происходит два действия. Первым шагом в реестре регистрируется новый сервис, а вторым — производится его старт. В качестве подтверждения в ветке реестра
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
должен появиться новый раздел giveioex, содержащий все параметры нового драйвера (рис. 9.4).
Рис. 9.4. Регистрация драйвера в реестре
Попытавшись теперь обратиться к порту $3F8, мы обнаружим, что доступ открыт и никаких ошибок не возникает.
Остается только убедиться, что мы можем предоставить доступ к портам не только своей программе, но и другой. Введем в поле Путь к ЕХЕ-файлу название этой же программы, например, е: \Test\Projecti.exe. Как мы помним, простой ее запуск и попытка обращения к порту приводили к ошибке. Запускаем и пробуем обратиться к порту... Ошибка не возникает! Можете попробовать запустить любую программу, требующую доступа к портам, и она будет работать.
Для того чтобы не таскать файл драйвера вместе с файлом программы, удобнее встроить его внутрь ЕХЕ-файла программы. Для этого с помощью любого редактора ресурсов нужно создать файл GivelOEx.RES, содержащий бинарный код драйвера. Затем необходимо подключить ресурсный файл к программе, и, при старте драйвера, создавать временный файл giveioex.sys, загружать его и удалять. Пример модуля, загружающего образ драйвера из файла ресурса, показан в листинге 9.11 (для экономии места мы снова показали только отличия этого модуля от модуля Giveio. Pas). Дня его использования необходимо подключить к основному модулю файл GiveIO_R вместо Giveio, а FGiveio.Createservice вызывать без параметров (листинг 912).
Листинг 9.11« Модуль Give工 JR, загружающий образ драйвера GivelO из файла ресурса
unitGiveIO_R;
interface
Uses Windows, SysUtils, Classes;
type
TGivelO = class
Private
Function CreateFileFromResource(FName : String) : Boolean;
Public
Function CreateService : Boolean;
Function RemoveService : Boolean;
Function StartService : Boolean;
Function StopService : Boolean;
Procedure GiveI0F0rCurrentProcess;
Procedure GiveloForProcess(dwprocessld: Cardinal);
End;
irrplementation
Uses WinSvc, Dialogs;
Const
DriverName : PChar= 'givei。'#。;
{ Подключение файла ресурсов } {$R GivelO.RES}
function GetSystemDir : String; var
Buffer: array[0..1023] of Char;
begin
SetString(Result, Buffer, GetSystemDirectory(Buffer, SizeOf(Buffer))); end;
function GetDriverPath : String;
begin
Result:= GetSystemDir + *\giveioex.sys'; end;
Function TGivelO.CreateService : Boolean;
var IpServiceArgVectors, P : PChar;
hSCMan, hSer vice: SC_HANDLE;
DriverPath : String;
Begin
Result:= False;
Driver Pa th := GetDriverPath;
CreateFileFromResource (DriverPath);
End;
Function TGivelO.RemoveService : Boolean; var servicestatus : TServiceStatus;
hSCMan, hService : SJHANDLE;
DriverPath : String;
Begin
Result:= False;
DriverPath := GetDriverPath;
Windows.DeleteFile(PChar(DriverPath));
End;
Function TGivelO. CreateFileFromResource (FTJarne : String) : Boolean;
Var S: TFileStream; Rsrc: HRSRC; Res: THandle;
Data: Pointer;
Begin
Result := False;
Rsrc := FindResource(HInstance, MakelntResource(102), RT_RCDATA); If Rsrc = 0 then Exit;
Res:= LoadResource(HInstance, Rsrc);
Try
Data := LockResource(Res);
If Data <> nil then
Try
S:= TFileStream.Create(FTJame, fmCreate);
Try
S.VJriteBuffer (Data,4/ Si zeOfResource (HInstance, Rsrc)); Finally
S.Free;
End;
Result:^ True;
Finally
UrilockResource (Res);
End;
Finally
FreeResource(Res);
End;
End;
end.
Листинг 9.12. Использование Giveio_R
unit Unitl;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, GiveIO_R;
type
TForml = class(TForm)
end;
{Регистрация сервиса в реестре и старт} procedure TForml.btnLoadClick(Sender: TObject); begin
FGivelO.CreateService;
FGiveTO.StartService;
end;
end.
Еще немного
о прямом доступе к портам
Еще одна возможная причина необходимости прямого доступа — время выполнения команд ввода/вывода. Проще говоря, драйвер GivelO изменяет карту разрешения ввода/вывода (IOPM, I/O Permission Мар), "отключая'' механизм защиты Windows. На проверку защиты требуется процессорное время, а, отключая защиту, мы экономим на этих проверках. Робертс в работе [12] приводит некоторые опенки. На выполнение команды ввода/ вывода тратится (процессор 486):
в DOS-режиме 16 тактов;
в режиме ядра — 10 тактов;
DOS-программа в режиме консоли NT — 29 тактов;
О Windows-программа через драйвер — от 6000 до 12 000 тактов (!);
Windows-программа в режиме прямого доступа — 30 тактов.
Однако вслед за Робертсом мы должны предупредить обрадовавшихся свободе программистов —— не злоупотребляйте прямым доступом к порту! Система защиты придумана не зря и предусматривает жесткую синхронизацию драйверов и устройств. Прямой доступ может привести к сбою синхронизации и, в конечном итоге, краху системы. Кроме тог。,следует помнить, что драйвер прямого доступа открывает доступ к портам, но не разрешает выполнение всех привилегированных инструкций, таких как sti (разрешение прерываний) и сы (запрет прерываний). Такие инструкции могут быть выполнены только драйвером режима ядра.
И еще об одном недостатке драйвера GivelO. Как видно из исходного кода, этот драйвер использует три недокументированных функции Windows. И, следовательно, гарантировать его работу в последующих версиях Windows нельзя. Еше раз повторим, что драйвер прямого доступа нужен для решения совершенно определенной задачи ——быстрого переноса программ из Windows 98 в Windows NT/2000/XP. Но при написании новых программ пользоваться им не следует.
Глава 10
Использование функций Windows
Прямое обращение к портам, конечно, не составляет большого труда и очень напоминает DOS-программирование. Однако это скорее обходной маневр. Более корректно использовать функции Windows, которая предоставляет множество функций для работы с поеледовательными портами.
Обзор функций Windows для работы с последовательными портами
Полное описание функций Windows приводится в третьей части книги (см. г/. 20, 21), а здесь мы рассмотрим основные принципы их использования. Windows работает с портами так же как с файлами. Для открытия порта используется функция CreateFile, а ДЛЯ закрытия ——CloseHandle. Для ЧТеНИЯ и передачи данных также используются файловые функции ReadFiie и WriteFiie соответственно. В Windows NT/2000/XP можно использовать асинхронные функции ReadFiieEx и WriteFiieEx (см. гл. 13). Кроме файловых функций для коммуникационных портов, Windows предоставляет также специальные функции.
Для использования порта необходимо получить его идентификатор — дескриптор порта, с помощью которого будут происходить все остальные обращения к порту. После использования надо вызвать функцию CloseHandle для освобождения дескриптора и закрытия порта. Windows не поддерживает разделение коммуникационных ресурсов, поэтому с момента вызова createFiie до вызова CloseHandle программа получает порт в свое полное владение. Вызов CreateFiie из другой программы, попытавшейся открыть уже занятый ресурс, вернет ошибку.
В отличие от файловых операций, перед началом работы с портом необходимо настроить такие параметры обмена, как: скорость обмена, параметры
четности, число стоп-бит и т. д. Кроме обычных параметров, уже изученных нами при работе в DOS, существует несколько специс1)ичных для Windows параметров ——тайм-ауты. Тайм-ауты позволяют настраивать как интервалы между принимаемыми байтами, так и общее время приема сообщения.
Основные параметры последовательного порта описываются структурой dcb, а тайм-ауты —— структурой commtimeouts. Настройка порта сводится к заполнению полей ЭТИХ структур И вызову функций настройки SetCommState н setCommTimeouts. Подробное описание полей всех структур можно найти B乙*20.
Если заполнение полей структуры вызывает затруднение (а как видно из описания структуры, полей в ней очень много), можно воспользоваться функцией BuiidCommDCB, которая позволяет заполнить поля структуры DCB на основе строки, по синтаксису аналогичной строке команды mode (см. прт 11). Кроме того, можно вызвать функцию Getcommstate, возвращающую текущие параметры порта, изменить нужные из них и вызвать SetCommState для задания новых параметров.
С ПОМОЩЬЮ ФУНКЦИИ BuildCommDCBAndTinieouts ПОЛЯ структур DCB И commtimeouts можно заполнить одновременно.
Для настройки порта существует еше одна функция. В специальной динамической библиотеке (DLL) содержится функция CommConfigDialog, ОТОбражающая стандартное диалоговое окно настройки порта. Функция возвращает структуру commconfig, а непосредственное задание полученных параметров производится с помощью Setcommconfig. Так же как и для dcb, можно сначала вызвать функцию Getcoranconfig, поменять нужные ПОЛЯ и вызвать SetcommConfig для установки новых параметров.
В диалоговом окне, отображаемом при вызове CommConfigDialog, присутствует кнопка Reset То Default (в русском варианте ее название переведено не совсем корректно как "Вернуть исходные значения'*). При нажатии на нее поля диалогового окна замещаются на принятые в системе параметры по умолчанию. Чтобы считать и изменить параметры по умолчанию, применяются функции GetDefaultCommConfig И SetDefaultCorrnnConfig. Заметим, ЧТО вызов этих функций не меняет настроек порта, а лишь записывает параметры по умолчанию во внутренние области коммуникационного драйвера.
Специальная настройка порта
В большинстве случаев достаточно тех настроек, которые дают структуры dcb и commtimeouts. Однако при использовании специальных устройств, подключаемых к порту компьютера, может потребоваться более тонкая настройка.
Информацию о возможностях ком мун и каци oi 1 ного порта и драйвера дает структура commprop. Подробное описание ее полей дается в гл. 20.
Получить информацию об устройстве в виде структуры commprop можно с ПОМОЩЬЮ функции GetComiriProperties.
Для изменения длины внутренних буферов коммуникационного драйвера применяется функция Sstupcomm. Все что она делает, это устанавливает размеры в байтах очередей приема и передачи. Следует помнить, что это только ре коме ндуе м ые значения. Указанные размеры будут приняты драйвером к сведению, но драйвер может корректировать эти значения при необходимости или совсем отвергнуть их. В последнем случае функция SetupCoiP£ завершится с ошибкой. Обычно внешние устройства обмениваются с драйвером достаточно короткими сообщениями, и, следовательно, вызов setupCoirai не требуется. Однако если внешнее устройство обменивается пакетами данных длиной несколько тысяч байт, рекомендуется установить соответствующие размеры очередей, чтобы избежать потери данных.
Получение состояния линий модема
В DOS для получения состояния индикаторов модема мы проверяли состояние порта MCR (адрес BasePort 1-4). В Windows для этого нам нужно будет вызывать функцию GetcoriniModemstatus. Подробное описание этой (функции можно найти в zz. 21.
Используем функции Windows
Модифицируем нашу программу, заменив прямое обращение к портам вызовами функций Windows.
Внешний вид основной формы изменится не намного. Во-первых, простую кнопку Инициализация мы заменим двумя ——Открытие порта и Закрытие порта. Нажатие этих кнопок позволит нам получать коммуникационный порт в полное владение и освободить его после работы. Во-вторых, мы заменим таблицу стандартных скоростей обмена набором констант cbr__xxx. Конечно, можно использовать любые числовые значения, но мы будем пользоваться только описанными в Windows константами скоростей обмена. И последнее изменение: два набора опций, касающихся контроля четности, мы заменим одним обшим набором.
В результате наших изменений основная форма должна получиться, как показано на рис. 10 I.
Для доступа к порту мы напишем класс TComPort, скрывающий все тонкости работы с Windows API. Исходный код этого класса и его использование приводятся в листингах 10.1 и 10.2 соответственно. Заметим, что в некою-
рых случаях мы специально упрощали код, делая его, может быть, немного более примитивным, в угоду читаемости и простоты. В следующих главах мы будем усложнять этот класс, добавляя в него новые возможности.
Рис. 10.1. Главное окно первой программы, использующей функции Windows
[Листинг ЮЛ. КлКсс TComPoft» использующий функции Windows
;:e,**・・.,g,・.-・*・**1af *w・・、vi*・GaF*ya、・・、,・,*7««・・・*№・・"”"”・*'*..・・;*”《・;**・・'*、'*w*・・《«**e*~・・、**・《<.—■■•***”《・~、・・・・**”・・・・.・・《・・.”v7~~.|>"・”・—••■■”
unit ComPort;
interface
uses
Windows, SysUtils, Classes;
Type TBaudRate = (
brllO, ЬгЗОО, ЬгбОО, Ьг1200, Ьг2400, Ьг4800, Ьг96ОО, Ьг14400, Ьг19200, Ьг38400, ЬгббООО, Ьг57600, Ьг115200 ); TByteSize = (
Ьз5, Ьзб, bs7, bs8
);
TParity =(
ptNONE, ptODD, ptEVEN, ptMARK, ptSPACE );
TStopbits =(
sblBITS, sblHALFBITS, sb2BITS
);
TComPort = class(TConponent) Protected
FBaseAddres s : VJord;
{базовый адрес порта}
FHandle
:THandle;
{дескриптор порта}
E'BaudRate
: TBaudRate;
{скорость обмена (бод)}
FByteSize
:TByteSize;
{число бит в байте}
FParity
:TParity;
{четность}
FStopbits
:TStopbits;
{имело стоп-бит}
Procedure
DoOpenPort; (открытие порта}
Procedure
DoClosePort; {закрытие порта}
Procedure
ApplyComSettings; {установка параметров
Procedure
ApplуComTimeouts; {установка тайм-аутов
Private
порта} порта}
FComNumber: Integer;
function GetConnected: Boolean;
procedure procedure procedure procedure procedure procedurePublicprocedure procedure procedure procedure procedure procedurePublicSetConnected(const Value: Boolean); SetComNumber(const Value: Integer); SetBaudRate(const Value: TBaudRate); SetByteSize(const Value: TByteSize); SetParity(const Value: TParity);
procedure procedure procedure procedure procedure procedure
Public
procedure procedure procedure procedure procedure procedure
Public
SetStopbits(const Value: TStopbits);
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
Public
{Открывает/закрывает порт}
Procedure Open;
Procedure Close;
{Возвращает True, если порт открыт}
Function Connect : Boolean;
{Возвращает структуру состояния порта ComStat, а в } {переменной CodeError возвращается текущий код ошибки } Function GetState(var CodeError : Cardinal) : TComSrat; {Возвращает состояние модема} Function GetModemState : Cardinal;
{Читает один байт. Если байт прочитан успешно, возвращает True} Function ReadByte(var В : Byte) : Boolean;
{Передает один байт. В случае успеха возвращает True} Function VJriteByte(const В : Byte) : Boolean;
Public
{Номер порта. При изменении порт переоткрывается, если был открыт}
Property ComNumber : Integer read FComNinnber write SetComNiiniber;
{Возвращает True, если порт открыт}
Property Connected : Boolean read GetConnected write SetConnected;
{Скорость обмена}
Property BaudRate {Число бит в байте} Property ByteSize {Четность} Property Parity {Число стоп-бит} Property Stopbits End;Property BaudRate {Число бит в байте} Property ByteSize {Четность} Property Parity {Число стоп-бит} Property Stopbits End;TBaudRateTBaudRateTByteSizeTByteSizeTParityTParityread FBaudRate write SetBaudRate;read FBaudRate write SetBaudRate;read FByteSize write SetByteSize;read FByteSize write SetByteSize;read FParity write SetParity;read FParity write SetParity;TStopbits read E"Stopbits writeTStopbits read E"Stopbits writeSetStopbits;SetStopbits;
Property BaudRate {Число бит в байте} Property ByteSize {Четность} Property Parity {Число стоп-бит} Property Stopbits End;
Property BaudRate {Число бит в байте} Property ByteSize {Четность} Property Parity {Число стоп-бит} Property Stopbits End;
TBaudRate
TBaudRate
TByteSize
TByteSize
TParity
TParity
read FBaudRate write SetBaudRate;
read FBaudRate write SetBaudRate;
read FByteSize write SetByteSize;
read FByteSize write SetByteSize;
read FParity write SetParity;
read FParity write SetParity;
TStopbits read E"Stopbits write
TStopbits read E"Stopbits write
SetStopbits;
SetStopbits;
implementation
Const
WindowsBaudRates: arrayfbrllO..brll5200] of DWORD =(
CBR_110, CBR—300, CBR—600, CBR_1200, CBR_2400, CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400, CBR_56000, CBR57600, CBR_115200 {CRB__128000, CBR_256000 — описаны в Windows .pas, но не использ^/ются} );
Const
=$00000001;=$00000001;dcb_Binary
=$00000001;
=$00000001;
dcb_ParityCheck
=$00000002;
dcb_OutxCtsFlow
=$00000004;
dcb_OutxDsrFlow
=$00000008;
dcb_Dt rCont rolMask
=$00000030;
dcb_DtrControlDisable
=$00000000;
dcb_Dt rContro1Enable
=$00000010;
dcb_DtrControLHandshake
=$00000020;
dcb_DsrSensivity
=$00000040;
dcb_TXCont inueOnXo f f
=$00000080;
dcb_OutX
=$00000100;
dcb_InX
=$00000200;
dcb_ErrorChar
-$00000400;
dcb_NullStrip
二 $00000800;
dcb_Rt s Cent rolMa s к
=$00003000;
dcb_Rt sControlDisable
=$00000000;
dcb_RtsControlEnable
=$00001000;
dcb_RtsControlHandshake
=$00002000;
dcb_RtsControlTogg1e
=$00003000;
deb_Abo r t OnE г го r
=$00004000;
deb Reserveds
=$FFFF8000;
Constructor TComPort.Create(AOwner : TCowponent);
Begin
Inherited Create(AOwner);
FHandle: = INVALID__HANDLE_VALUE;
End;
Destructor TComPort.Destroy;
Begin
DoClosePort;
Inherited Destroy;
End;
function TComPort.GetConnected: Boolean;
begin
Result := (FHandle <> INVALlD_bWDLE_VALUE); end;
procedure TComPort.SetConnected(const Value: Boolean); begin
If Value then DoOpenPort else DoClosePort; end;
Function TCcmPort.Connect : Boolean;
Begin
DoOpenPort;
Result: = Connected;
End;
Procedure TComPort. Open;
Begin
DoOpenPort;
End;
Procedure TComPort. Close;
Begin
DoClosePort;
End;
procedure TComPort.SetComNumber (const Value: Integer);
var Active : Boolean;
begin
If FCorriNumber = Value then Exit;
Active:= Connected; {сохраним значение активности порта}
If Active then DoClosePort; {закрыть порт перед изменением индекса} FCctnNumber:= Value; {устанавливаем новое значение номера порта} If Active then DoOpenPort; {открыть порт, если он был открытI end;
{открытие порта}
Procedure TComPort. DoOpenPort;
Var ComName : String;
Begin
If Connected then Exit;
{Для портов 1—9 можно исполь зовать простые имена С0М1—С0М9, }
{ но для портов 10—256 надо писать полное имя. Для общности 」
{ будем всегда исполь зовать полное имя порта}
ComNam6:= Format (* \ \. \COM%-d *, [FComNumber J );
{Открытие последователь ного порта }
FHandle:= CreateFile(
PChar(CowName), {передаем имя открываемого порта) GENERHC_READ or GENERIC_WRITEZ {ресурс для чтения и записи} О, { неразделяемый ресурс } nil, { Нет атрибутов защиты }
OPEN_EX工STING, {вернуть сшибку, если ресурс не существует} FILE_ATTRIBUTE_NORMAL, {синхронный режим доступа}
О { Должно быть 0 для СОМ портов }
);
{Если ошибка открытия порта — выход}
If not Connected then Exit;
{Инициализация порта|
ApplyConiSettings;
End;
{закрытие порта} Procedure TComPort.DoClosePort; Begin
If not Connected then Exit;
{Освобождение дескриптора 1 CloseHandle(FHandle);
{Сброс дескриптора порта} FHandle:= INVALID_HANDLEJ7ALUE;
End;
{установка параметров порта}
Procedure TComPort.ApplуComSe11ings;
Var deb: TDCB; {структура описания параметров порта} Begin
If not Connected then Exit;
{ Чистка структуры , FillChar(deb, SizeOf(deb), 0);
{ Пеле DCBLength должно содержать размер структуры } deb.DCBLength:^ SizeOf(deb);
{ Скорость обмена (бод) }
deb.BaudRate := WindowsBaudRates[FBaudRate];
{ Windows не поддерживает небинарный режим работы последователь ных портов }
deb. Flags := dcb_Binary;
{ Число бит в байте }
deb.ByteSize := 5 十 Ord(FByteSize);
{ Контроль четности }
deb.Parity:= Ord (FParity);
{ Число стоп бит }
deb. StopBits := Ord(FStopbits);
{ Установить новые настройки порта }
SetCommState (FHandle, deb);
End;
(установка тайм-аутов порта}
Procedure TComPort. ApplуComTimeout s;
Begin
If not Connected then Exit;
End;
procedure TComPort. SetBaudRate (const Value: TBaudRate);
begin
If FBaudRate = Value then Exit;
FBaudRate: = Value;
Appl yComS e 11 i ng s;
end;
procedure TComPort. SetByteSize (const Value: TByteSize);
begin
If FByteSize = Value then Exit;
FByteSize:= Value;
Appl yComSe 11 i ng s;
end;
procedure TComPort. Set Parity (const Value: TParity);
begin
If FParity = Value then Exit;
FParity := Value;
ApplyComSet t ings;
end;
procedure TComPort.SetStopbits(const Value: TStopbits);
begin
If FStopbits = Value then Exit;
FStopbits := Value;
ApplyComSettings;
end;
Function TComPort.GetState(var CodeError : Cardinal) : TComStat;
Begin
{Возвращает структуру состояния порта и код ошибок} ClearCommError(FHandle, CodeError, ©Result);
End;
Function TComPort.ReadByte(var В : Byre) : Boolean;
Var RealRead : Cardinal;
Begin
{Используем синхронное чтение. В случае успешного чтения}
{функция ReadFile вернет True, а переменная RealRead, } {хранящая число реально прочитанных байт, будет равна 1}
Result:= False;
If ReadFile(FHandle, В, 1, RealRead, nil) then Result:= (RealRead = 1);
End;
Function TComPort.WriteByte(const В : Byte) : Boolean;
Var RealWrite : Cardinal;
Begin
{Исполь зуем синхронного запись. В случае успешной передачи}
{функция WriteFile вернет True, а переменная RealWrite, }
{хранящая число реально переданных байт, будет равна 1}
Result:= False;
If WriteFile(FHandlez В, 1, RealWrite, nil) then Result:= (RealWrite = 1);
End;
Function TComPort.GetModemState : Cardinal; Begin
{Возвращает состояние модема}
GetCcarniModemStatus (FHandle, Result);
End;
end.
:•♦“"”,••”,"•♦•.-♦*•"•;• r":"~"EV”》・ ••,•”-'••■"•• •»"""".";?",『•■•「■:,""•■ f ••,•"
Листинг 10.2. Использование класса TComPort
unit Unitl;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, C]asses, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtr]sf Buttons, Spin, Mask, ToolEdit, Comport, ComCtrl s;
type
TForml = class (TE'orm)
Pane11: TPanel;
Pane12: TPanel; btnClose: TBitBtn; ReadTimer: TTime r; ParamPane1: TPane1; rgPortIndex: TRadioGroup; rgDataLen: TRadioGroup; rgStopBits: TRadioGroup; rgBaud: TRadioGroup; rgParity: TRadioGroup; GroupBox1: TGroupBox; bRead: TSpeedButton; Panel3: TPane1;
Bevel2: TBevel; DR: TShape; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; Label?: TLabel;
Label9: TLabel;
Labe110: TLabe1;
Label8: TLabe1;
Labelll: TLabel;
OE: TShape; PE: TShape; FE: TShape; BD: TShape; DCD: TShape;
RI: TShape;
CTS: TShape; DSR: TShape; TERI: TShape; Label12: TLabel;
eReadBuffer: TEdit; btnClearReadBuf: TSpeedButton; GroupBox2: TGroupBox;
btnSend: TSpeedButton; seSendByte: TSpinEdit; btnOpenPort: TSpeedButton; btnClosePort: TSpeedButton; StatusBar: TStatusBar;
eAvaibleBytes: TEdit;
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedureprivateprocedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedureprivateFormCreate(Sender: TObjecr); btnCloseClick(Sender: TObject); bReadClick(Sender: TObject); ReadTimerTimer(Sender: TObject); FormDestroy(Sender: TObject); btnSendClick(Sender: TObject); btnClearReadBufClick(Sender: TObject); btnOpenPortClick(Sender: TObject); btnClosePortClick(Sender: TObject); rgDataLenClick(Sender: TObject); rgStopBitsClick(Sender: TObject); rgParityClick(Sender: TObject); rgBaudClick(Sender: TObject);
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure
private
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure
private
его }его }{ Компонент для доступа к порту. Будем создавать { динамически.
его }
его }
FPort : TComPort;
public
procedure SetPortproperties;
procedure SetEnabledControls;
end;
var
Forml: TForml;
implementation
{$R *.DFM}
{ Создание компонента доступа к портам при создании формы } procedure TForml. FormCreate (Sender: TObj ect);
begin
FPort: = TComPort.Create (Self);
SetEnabledControls;
end;
{Вызывается при уничтожении формы (выходе из программы) } procedure TForml. FormDestroy (Sender: TObject);
begin
FPort. Free;
end;
{Кнопка открытия порта}
procedure TForml.btnOpenPortClick(Sender: TObject);
begin
FPort. Close;
FPort. ComNumber:= rgPortIndex.Itemlndex+l;
SetPortProperties;
FPort .Open;
If FPort.Connected then
StatusBar.Panels[0].Text:= 1 Порт успешно открыт *
Else
StatusBar.Panels[0].Text:= 'Ошибка открытия порта *;
SetEnabledControls;
end;
procedure TForml.btnClosePortClick(Sender: TObject);
begin
FPort.Close;
StatusBar.Panels[0].Text:= * *;
S etEnabledContго1s;
end;
procedure TForml.SetEnabledControls; Var Active : Boolean;
begin
Active:= FPort.Connected;
btnClosePort.Enabled:= Active;
=not Active;=not Active;=Active;=Active;=Active;=Active;=not Active;=not Active;=Active;=Active;=Active;=Active;rgPortIndex.Enabled btnOpenPort.Enabled rgDataLen.Enabled rgStopBits.Enabled rgParity.Enabled rgBaud.Enabled
=not Active;
=not Active;
=Active;
=Active;
=Active;
=Active;
=not Active;
=not Active;
=Active;
=Active;
=Active;
=Active;
end;
{Задание свойств порта}
procedure TForml.SetPortProperties; begin
rgDataLenClick(nil);
rgStopBitsClick(nil);
rgParityClick(nil);
rgBaudClick(nil);
end;
{Кнопка закрытия программы}
procedure TForml.btnCloseClick(Sender: TObject);
begin
Close;
end;
{Кнопка опроса порта}
procedure TForml.bReadClick(Sender: TObject);
begin
ReadTimer. Enabled: = bRead. Down; end;
Const {Цвета индикаторов}
IColor : Array [Boolean] of TColor = (clVJhite, clGreen);
procedure TForml.ReadTimerTimer (Sender: TObject);
Var В : Byte; S : String; Currentstate : TComStat; AvaibleBytes, ErrCode, ModemState : Cardinal;
begin
{Получает состояние порта }
Currentstate:= FPort.GetState(ErrCode);
{ Отображаем число полученных, но еще не прочитанных байт} AvaibleBytes:= СигrentState.cblnQue;
eAvaibleBytes. Text: = IntToStr (AvaibleBytes);
If AvaibleBytes > 0 then begin
If FPort.ReadByte(B) then begin { получение байта } S:= eReadBuffer.Text;
S:= S + Format(,$%X,,[B]);
If Length(S) > 40 then Delete(S, 1, 3); eReadBuf fer.Text:= S;
End;
End;
{Отображение индикаторов состояния порта}
{ Данные готовы }
DR.Brush.Color:= IColor[AvaibleBytes > 0];
{ Переполнение
0E. Brush.Color:= IColor[(ErrCode0E. Brush.Color:= IColor[(ErrCodeandandCE OVERRUN )CE OVERRUN ){ Ошибка паритета }PE.Brush.Color:= IColor[(ErrCode and CE_RXPAR工TY) { Ошибка кадра}FE.Brush.Color:= IColor[(ErrCode and CE_FRAME ) { Обрыв линии}BD.Brush.Color: = IColor[ (ErrCode and CE_BREAK ){ Ошибка паритета }PE.Brush.Color:= IColor[(ErrCode and CE_RXPAR工TY) { Ошибка кадра}FE.Brush.Color:= IColor[(ErrCode and CE_FRAME ) { Обрыв линии}BD.Brush.Color: = IColor[ (ErrCode and CE_BREAK )<> 0];<> 0];<> 0];<> 0];<> 0];<> 0];<> 0];<> 0];
0E. Brush.Color:= IColor[(ErrCode
0E. Brush.Color:= IColor[(ErrCode
and
and
CE OVERRUN )
CE OVERRUN )
{ Ошибка паритета }
PE.Brush.Color:= IColor[(ErrCode and CE_RXPAR工TY) { Ошибка кадра}
FE.Brush.Color:= IColor[(ErrCode and CE_FRAME ) { Обрыв линии}
BD.Brush.Color: = IColor[ (ErrCode and CE_BREAK )
{ Ошибка паритета }
PE.Brush.Color:= IColor[(ErrCode and CE_RXPAR工TY) { Ошибка кадра}
FE.Brush.Color:= IColor[(ErrCode and CE_FRAME ) { Обрыв линии}
BD.Brush.Color: = IColor[ (ErrCode and CE_BREAK )
<> 0];
<> 0];
<> 0];
<> 0];
<> 0];
<> 0];
<> 0];
<> 0];
{Отображение индикаторов состояния модема} Modems tate := FPort. Ge tModemS tate;{Отображение индикаторов состояния модема} Modems tate := FPort. Ge tModemS tate;
{Отображение индикаторов состояния модема} Modems tate := FPort. Ge tModemS tate;
{Отображение индикаторов состояния модема} Modems tate := FPort. Ge tModemS tate;
{Сигнал DCD (data carrier detect)}
DCD.Brush.Color :
:=IColor[(Modemstate and MS_RLSD
_ON) <>
0];
{Состояние линии
R工(ring indicator)}
RI .Brush.Color :
:=IColor[(ModemState and MS_R工NG
_°N) <>
0];
{Состояние линии
DSR (data set ready)}
DSR.Brush.Color :
:=IColor[(ModemState and MS_DSR_<
ON ) <>
0];
{Состояние линии
CTS (clear to send)}
CTS.Brush.Color :
:=IColor [ (ModemState and MS_CTS_<
ON ) <>
0];
{ Сигнал "спад огибающей** (окончание звонка) }
{ Не определяется в Windows }
{TERI.Brush.Color:= IColor[(Currentstate and $04) <> 0];} end;
{Очистить буфер чтения}
procedure TForml.btnClearReadBufClick(Sender: TObject); begin
eReadBuffer.Text:= 1 *;
end;
{Отправка байта}
procedure TForml.btnSendClick(Sender: TObject);
begin
FPort.WriteByte(seSendByte.Value);
end;
{Длина байта}
procedure TForml.rgDataLenClick(Sender: TObject);
begin
FPort.ByteSize:= TByteSize(rgDataLen.Itemindex); end;
{Число стоп-бит. Так как Windows оставляет контроль за} {правильностью настроек на программиста, то запрещаем } {выставлять 2 стоп-бита для не 5-битовых данных}
procedure TForml.rgStopBitsClick(Sender: TObject);
begin
Case rgStopBits.Itemindex of
0: // 1 стоп-бит
FPort.Stopbits:= sblBITS;
1: // 2 (1.5) стоп-бита
If rgDataLen.Itemindex = 0 then FPort.Stopbits:= sblHALFBITS
Else FPort.Stopbits:= sb2BITS;
End;
end;
{Контроль четности}
procedure TForml.rgParityClick(Sender: TObject);
begin
FPort.Parity := TParity (rgParity. Itemlnciex);
end;
{Скорость передачи}
procedure TForml.rgBaudClick(Sender: TObject);
begin
FPort. BaudRare:= TBaudRate(rgBaud.Itemindex); end;
end.
Проделаем несколько экспериментов с созданной программой. Запустим, как и в прошлый раз, две копии нашей программы. В первом окне откроем порт COMI, выбрав в параметрах СОМ1 и нажав кнопку Открыть порт. Попробовав открыть тот же порт во втором окне, мы увидим сообщение Ошибка открытия порта. Как мы уже говорили, Windows не позволяет двум приложениям использовать один ком мун и кац и он н ы й ресурс.
Откроем в первом окне порт СОМ 1, а во втором COM2. Настроив порты на одинаковые характеристик% мы легко передадим байт из одного порта в другой (как и в предыдущей главе, в одном окне нажмем кнопку Опрос порта, а во втором — Отправить). Настроив порты на разные характеристики, можно увидеть индикацию ошибок четности и переполнения.
Остается отметить, что наша новая программа работает в любой версии Windows.
10.5. Несколько замечаний
о контроле четности
Наша программа позволяет сделать несколько интересных наблюдений о принципах контроля четности.
Включим в программе передатчика контроль четности чет, а в программе приемника нечет. Отправим байт из первой программы во вторую. Видно, что мигают индикаторы DR ('*Данные готовы**) и РЕ ("Ошибка паритета").
Теперь выключим проверку четности (т. е. посылку контрольного бита) в передатчике, а в приемнике оставим контроль чет. Видно, что ошибка паритета будет зависеть от значения передаваемого байта. При передаче числа 26 ошибку не видно, а при передаче числа 24 ошибка однозначно возникает.
Контроль четности дополняет байт нужным битом —— при контроле чет байт дополняется до четного числа бит, при контроле нечет ——до нечетного числа бит, при контроле постоянно 0 ——всегда дополняется нулевым битом, а при контроле постоянно 1 — всегда единичным битом.
Таким образом, контроль четности может распознать ошибку только в случае ошибки в одном бите. Если байт будет передан с двумя ошибочными битами, контроль четности не отработает. Для повышения достоверности данных необходимо использовать методы, описанные в гл. 2
Глава 11
Использование потоков
Использование функций Windows для обращения к портам не избавляет нас от необходимости снова опрашивать порт в цикле, как мы делали это в среде DOS без прерываний. Конечно, в Windows мы вое пол ьзовал и сь таймером, но у этого метода есть недостаток ——сообщения таймера имеют низший приоритет и обрабатываются в последнюю очередь. В этой главе мы воспользуемся другими механизмами Windows и создадим для чтения порта отдельный поток.
Преимущества потоков
Опрос порта по таймеру имеет существенный недостаток. Сообщениям таймера присваивается низший приоритет, и они обрабатываются в последнюю очередь. В Windows 95/98, например, достаточно удерживать мышкой заголовок окна программы, и обработка таймера будет заморожена.
Избавиться от недостатка таймера позволит использование потоков. Потоки —отдельные процессы Windows. Потокам можно задавать разные приоритеты выполнения, приостанавливать их выполнение или совсем завершать их работу. Подробное описание работы с потоками выходит за рамки этой книги.
В Delphi процедура создания потоков очень проста. Достаточно создать класс от TThread и описать (перекрыть) метод Execute. При необходимости можно перекрыть конструктор и деструктор.
Создание потока опроса порта
Добавим в нашу последнюю программу из гп. 7〃 создание потока для опроса порта и чтения данных.
Во-первых, удалим таймер из основной формы, он нам больше не потребуется. В класс TComPort добавим свойство ReadActive, управляющее активностью чтения порта. Кроме того, нам нужно будет добавить событие onReadByte, обработчик которого будет вызываться при получении очередного байта. Разумеется, в реальной программе должно быть событие onReadData, обработчик которого вызывается при получении очерелвой порции данных. Сейчас же нас больше интересует сам поток для чтения порта.
Изменения в коде основной формы показаны в листинге 11.1, а изменения в коде компонента показаны в листинге 11.2. Полный код вы можете найти на прилагаемом к книге компакт-диске.
Листинг 11.1, Изменения в коде основного модуля для использования потоков
unit Unitl;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Buttonsr Spin, Mask, ToolEdit, ComPort, ComCtrls;
type
TForml = class(TForm)
private
{ Компонент для доступа к порту. Будем создавать его }
{ динамически.}
FPort : TComPort;
{Этот обработчик будет вызываться при получении байта} procedure OnReadByte(const В : Byte; Portstate : TComStat;
ErrCode, ModemState : Cardinal); public
procedure SetPortProperties;
procedure SetEnabledControls;
end;
var
Forml: TForml;
in^lementation
{$R *.DFM}
{ Создание компонента доступа к портам при создании формы } procedure TForml. FormCreate (Sender: TObject);
begin
FPort:= TComPort. Create (Self);
FPort. OnReadByte; == OnReadByte; {устанавливаем обработчик} SetEnabledCont rols;
end;
{Кнопка опроса порта}
procedure TForml.bReadClick (Sender: TObject);
begin
{Включить опрос порта}
FPort. ReadAct ive: = bRead. Down;
end;
Const {Цвета индикаторов}
IColor : Array [Boolean] of TColor = (clWhite, clGreen);
{Будет вызываться по приходу байта. Единственная неприятность -} {состояние линий и модема будет отражать состояние на момент } {получения байта, а не отображаться постоянно. Для постоянного} (отображения нужно создавать события OnModemStatusChange и } {OnPortStateChange. Однако сейчас наша задача проще.}
procedure TForml.OnReadByte (const В : Byte; Portstate : TComStat;
ErrCode, ModemState : Cardinal);
Var S : String; AvaibleBytes : Cardinal;
begin
{ Отображаем число полученных, но еще не прочитанных байт} AvaibleBytes:= Portstate.cb工nQue;
eAvaibleBytes.Text:= IntToStr(AvaibleBytes);
{Теперь нет необходимости проверять наличие байта для чтения,} {событие OnReadByte вызывается только после считывания байта }
S:= eReadBuffer.Text;
S:= S + Format(,$%X,, [B]);
If Length(S) > 40 then Delete(S, 1, 3); eReadBuffer.Text:= S;
end;
end.
j Листинг 11.2. Добавление потока для чтения данных в компонент тсотРогЬ
unit ComPort;
interface
uses
Windowsr SysUtils, Classes, Forms;
Type
{тип события при получении байта}
TReadByteEvent = procedure (const В : Byte; Portstate : TComStat;
ErrCode, Modemstate : Cardinal) of object;
TComPort = class; {опережающее описание}
{читающий поток}
TReadThread = class(TThread)
FOwner : TComPort;
FByte : Byte;
FPState : TComStat;
FMState : Cardinal;
FErrCode: Cardinal;
Protected
Procedure Execute; override;
Procedure DoReadByte;
Public
Constructor Create(AOwner : TComPort);
End;
TComPort = class (TComponent) Protected
FReadThread : TReadThread; {читающий поток}
Private
procedure SetReadActive(const Value: Boolean); function GetReadActive: Boolean;
Public
{Событие, вызываемое при получении байта}
Property OnReadByte : TReadByteEvent read FOnReadByte write FOnReadByte;
End;
impl ementat i on
Constructor TComPort.Create (AOwner : TComponent);
Begin
Inherited Create (AOwner);
FHandle:= INVALI D_HANDLE_VALUE;
FReadThread: = nil;
End;
{открытие порта}
Procedure TComPort. DoOpenPort;
Var ComName : String;
Begin
If Connected then Exit;
{Инициализация порта} ApplyComSettings;
{Создание читающего потока}
If not Assigned(FReadThread) then FReadThread:= TReadThread.Create(Self);
End;
{закрытие порта}
Procedure TComPort.DoClosePort;
Begin
If not Connected then Exit;
{Замораживаем поток чтения} ReadActive:= False;
{Уничтожение читающего потока} FReadThread.FreeOnTerminate:= True; FReadThread.Terminate;
FReadThread:= nil;
{Освобождение дескриптора порта} CloseHandle(FHandle);
{Сброс дескриптора порта}
FHandle:= INVALID_HANDLE_VALUE;
End;
{Активность чтения порта}
procedure TCcmPort.SetReadActive(const Value: Boolean); begin
If not Assigned(FReadThread) then Exit;
If Value then begin
If FReadThread.Suspended then FReadThread.Resume;
End else begin
If not FReadThread.Suspended then FReadThread.Suspend; End;
end;
function TComPort. GetReadActive: Boolean; begin
Result: = False;
If Assigned (FReadThread) then
Result: = not FReadThread.Suspended;
end;
{Класс TReadThread }
Constructor TReadThread. Create (AOwner : TComPort);
Begin
Inherited Create (True);
F0wner:= AOwner;
End;
Procedure TReadThread.Execute;
Var В : Byte; Currentstate : TComStat;
AvaibleBytes, ErrCode, ModemState : Cardinal;
Begin
Try
With FOwner do begin
While (not Terminated) and Connected do begin {пока порт открыт}
{Получаем состояние порта (линий и модема) } Currentstate:= GetState(ErrCode);
Modemstate := GetModemState;
{ Число полученных, но еще не прочитанных байт }
AvaibleBytes:= CurrentState.сЫnQue;
{ Проверка числа доступный байт}
If AvaibleBytes > 0 then begin
If ReadByte(В) then begin { получение байта }
{сохраняем параметры вызова события} FByte := В;
FPState := CurrentState;
FMState := Modemstate;
FErrCode:= ErrCode;
{Вызываем событие OnReadByte. Для синхронизации c VCL}
}}{надо вызвать метод Synchronize
}
}
Synchronize(DoReadByte);
End;
End;
Sleep(1);
End;
End;
Except End;
End;
{Вызывается для передачи события о приходе байта}
{в основной компонент через метод Synchronize }
Procedure TReadThread.DoReadByte;
Begin
With FOwner do begin
If As signed(FOnReadByte) then
FOnReadByte(FByte, FPState, FErrCode, FNState);
End;
End;
end.
При открытии порта МЫ создаем ПОТОК TReadThread, а при закрытии порта —уничтожаем его. Процедура закрытия порта (DoCiosePort) немного усложнилась. Сначала мы должны уничтожить читающий поток и только после этого можем закрыть порт и освободить дескриптор.
Второй ТОНКИЙ момент ——ВЫЗОВ события OnReadByte. Его вызов вынесен в отдельную процедуру (DoReadByte) не случайно. Мы знаем, что в обработчике onReadByte, находящемся в основной форме программы, мы будем использовать VCL-компоненты для отображения состояния порта и полученного байта. Но, с другой стороны, OnReadByte вызывается из другого потока, а, следовательно, поток основной формы и читающий поток должны быть синхронизированы. Синхронизация используемых в основной форме VCL-компонентов с читающим потоком достигается вызовом метода synchronize. Этот метод имеет единственный параметр — адрес процедуры, подлежащей синхронизации. В нашем примере мы передаем адрес процедуры DoReadByte, В Которой И вызываем событие OnBeadByte.
Еще одно замечание. Поскольку событие onReadByte вызывается только при получении нового байта, то и индикаторы статуса линии и модема будут обновляться только при получении очередного байта. Таким образом, инди-
кация будет отражать состояние порта на момент получения байта. При необходимости корректного отображения состояния порта нужно создавать события OnPortStatuschange И 0nModemStatusChange.
И напоследок маленькая "ложка дегтя”. Конечно, использование потоков сильно разгружает основной поток программы и позволяет опрашивать порт с большим приоритетом, но есть маленькая тонкость. Она заключается в вызове sleep(1) в конце процедуры Execute. Этот вызов замораживает работу потока на 1 мс и отдает управление другим потокам системы. Однако это одновременно означает, что в течение 1 мс порт опрашиваться не будет! Если же мы уберем эту строку, наш поток будет "съедатьи 100% процессорного времени, в чем легко убедиться, запустив системный монитор ресурсов (a Windows 98 просто “зависнет”). Вызов же заморозки процесса на 1 мс уменьшит загрузку процессора до 30—40%, что все равно довольно много. Решением этой проблемы мы займемся в следующей главе.
Глава 12
Функции асинхронного доступа и события
Использование потоков позволило нам разгрузить основную программу от постоянного опроса порта, а также обрабатывать порт независимо от действий в системе. Однако даже чтение порта в отдельном потоке еще не предел совершенства. Асинхронное чтение и применение событий позволит нам использовать ресурсы компьютера по минимуму — читающий поток будет *'просыпаться" только в случае появления данных для чтения или других нужных нам событий.
Асинхронный доступ
При синхронных операциях функции ReadFiie и writeFiie завершаются только после окончания операции или при ошибке. Асинхронный доступ подразумевает возможность начать операцию, а ее завершение обработать в другой момент времени. Таким образом, асинхронный вызов, например ReadFiie, начнет операцию чтения и сразу же завершит выполнение самой функции ReadFiie. Результат операции можно узнать, вызвав функцию GetOveriappedResuit. Более того, асинхронная операция связывается со специальным объектом-событием, который позволяет дожидаться завершения операции, остановив выполнение потока, т. е. практически без затрат процессорного времени. Ожидание производится с помощью обычных функций синхронизации WaitForSingleObject Н WaitForMultipleObjects.
Для использования асинхронных операций порт должен быть открыт с применением флага F工LE—FLAG—OVERLAPPED При ВЫЗОВС фуНКЦИИ CreateFile.
Если порт открыт без флага г I le_flag__ove r lap ped, to использование асинхронных операций в Windows 2000 вызовет зависание программы.
Кстати, если порт открыт с флагом file__flag_overlapped, а программа использует синхронные операции, то Windows 98 выполнит операции записи, a Windows 2000 просто проигнорирует такой вызов. Синхронный вызов операции чтения может вызвать зависание программы в цикле бесконечного ожидания.
Для использования асинхронных операций необходимо внести изменения в код нашей программы, приведенной в предыдущей главе. Измененный код показан в листинге 12.1.
i Листинг 12.1. Изменения в процедуре открытия порта для асинхронного ^доступа
(открытие порта}
Procedure TComPort.DoOpenPort;
Var CoroName : String;
Begin
If Connected then Exit;
{Для портов 1—9 можно использовать простые имена } {СОМ1—СОМ9, но для портов 10-256 надо писать } {полное имя. Для общности будем всегда использовать} {полное имя порта.}
ComName:= Format (* \ \. ХСОМ%^1, [FComNumber]);
(Открытие последовательного порта }
FHandle: = CreateFile(
PChar (ConiName), {передаем имя от крыв а емого порта} GENERIC_READ or GENER工C_WRITE, {ресурс для чтения и записи} 0, { не разделяемый ресурс } nil, { Нет атрибутов защиты }
0PEN_EX工ST工NG, {вернуть ошибку, если ресурс не существует} {асинхронный режим доступа}
FILEJYTTR工BUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0 { Должно быть 0 для COM-портов }
);
(Если ошибка открытия порта — выход} If not Connected then Exit;
{Инициализация порта}
ApplyComSett ings;
{Создание читающего потока}
If not Assigned(FReadThread) then
FReadThread:= TReadThread.Create(Self);
End;
Кроме открытия порта, нам нужно будет избавиться от синхронных операций чтения и записи данных. Код новой функции записи данных приводится в листинге 12.2, а операцией чтения мы займемся чуть позже.
;Листинг 12.2. Асинхронная запись данных
Function TComPort.WriteByte(const В : Byte) : Boolean;
Var Signaled, RealWrite, BytesTrans : Cardinal;
{структура для асинхронной записи}
WriteOL : TOverLapped;
Begin
Result:= False;
{создание события для асинхронной записи}
FillChar(WriteOL, SizeOf(WriteOL), 0);
WriteOL.hEvent:= CreateEvent(nil. True, True, nil);
Try
{начало асинхронной записи}
WriteFile(FHandle, В, 1, RealWrite, ©WriteOL);
{ожидание завершения а синхронной onepauidM}
Signaled:== WaitForSingleObj ect(WriteOL.hEvent, INFINITE);
{получение результата асинхронной операции}
Result :=
(Signaled = WAIT_ OBJECT_0) and
(GetOverlappedResult(FHandle, WriteOL, BytesTrans, False));
Finally
{освобождение дескриптора события}
CloseHandle(WriteOL.hEvent);
End;
End;
События последовательного порта
Опрос состояния порта в цикле ——не самый оптимальный способ определения наличия данных для чтения. В DOS мы научились использовать прерывания, позволяющие вызывать нужное нам событие только при получении нового байта. Мы могли управлять причинами возникновения прерываний, настроив вызов прерыванн访,например, по изменению состояния линий модема.
В Windows также есть механизм, похожий на систему прерываний DOS. Функция setcoimnMask позволяет настроить обработку следующих событий:
обрыв линии;
口 изменение состояния линии CTS;
изменение состояния линии DSR;
口 одна из ошибок: ce_frame, ce_ouerrun или ce_rxparity;
О сигнал RI;
口 изменение состояния RLSD;
принят символ;
получен специальный символ, описанный в блоке dcb;
буфер посылки опустошен.
Отличий этого набора условий от набора, предоставляемого DOS, немного, но они достаточно существенны. Во-первых, Windows позволяет настраивать события от CTS, DSR, RI и RLSD раздельно. Во-вторых, допускается раздельная обработка ошибок обрыва линии и формата данных. И последнее новшество, присутствующее исключительно в Windows, — в блоке dcb можно задать специальный символ, по приходу которого будет генерироваться событие. Это удобно, например, при обработке символа начала посылки.
Для ожидания наступления события используется функция WaitCommEvent, подробное описание которой приводится в гл. 21.
Опережая события, отметим, что с помощью вызова Devi с е i oCon t г о 1 можно установить еще одно условие: приемный буфер заполнен на 80% (флаг SERIAL_EV_RX8 0FULL, СМ. разд. 22.16).
Использование событий
Итак, возьмем наш пример из предыдущей главы и изменим функцию Execute читающего потока таким образом, чтобы читающий поток "просыпался'* только при получении символа, не занимая процессорное время без надобности.
Все изменения, которые нам потребуется сделать, касаются только класса TComPort и читающего потока:
П в функцию открытия порта следует добавить флаг file_flag_overlapped (мы уже сделали это для использования асинхронной записи);
создать сигнальный объект для асинхронных операций (для чтения нужно создать свой объект-событие);
установить маску наступления событий С ПОМОЩЬЮ SetCommMask;
добавить код ожидания наступления события;
口 переделать чтение байта в асинхронный вариант;
добавить обработку асинхронного чтения.
Несмотря на внушительный список, изменений не так уж и много. Исходный код новой функции Execute приводится в листинге 12.3, а полный код программы можно найти на прилагаемом к книге компакт-диске.
Листинг 12.3. Асинхронная запись данных
{Основная функция потока}
Procedure TReadThread.Execute;
Var В : Byte; Currentstate : TCcmStat;
AvaibleBytes, ErrCode, ModemState, RealRead : Cardinal;
ReadOL : TOverLapped; {структура для асинхронного чтения}
Signaled, Mask : DWORD;
BytesTrans : DWORD; {не используется для WaitCorranEvent}
Begin
With FOwner do begin
Try
{создание события для асинхронного чтения}
FillChar(ReadOL, SizeOf(ReadOL), 0);
ReadOL.hEvent:= CreateEvent(nil. True, True, nil);
{Маска событий, которые будет отслеживать читающий поток }
{Пока это толь ко получение символа}
SetConunMas k(FHandle, EV_RXCHAR);
While (not Terminated) and Connected do begin {пока порт открыт} { Ждем одного из событий } WaitCommEvent(FHandle, Mask, @ReadOL);
Signaled:= WaitForSingleObj ect(ReadOL.hEvent, INFINITE);
If (Signaled = WAIT_OBJECT_0) then begin If GetOverlappedResult(FHandle, ReadOL,
BytesTrans, False) then begin
{после GetOverlappedResult в переменной mask, } {которая передавалась в Wa itCorranEvent, появятся } {флаги произошедших событий, либо 0 в случае } {ошибки.}
If (Mask and EV_RXCHAR) о 0 then begin
{Получаем состояние порта (линий и модема)} Currentstate:= GetState(ErrCodej;
Modemstate := GerModemState;
{ Число полученных, но еще не прочитанных байт} AvaibleBytes:= Currentstate.cblnQue;
{ Проверка числа доступных байт}
If AvaibleBytes > 0 then begin
If ReadFile(FHandle, В, 1, RealRead, 0ReadOL) then begin {сохраняем параметры вызова события} FByte := В;
FPState := CurrentState;
FMState := ModemState;
FErrCode:= ErrCode;
{Вызываем событие OnReadByte. Для синхронизации c VCL} {надо вызвать метод Synchronize.}
Synchronize(DoReadByte);
End;
End;
End;
End;
End;
End;
Finally
{закрытие дескриптора сигнального объекта}
CloseHandle(ReadOL.hEvent);
{Сброс события и маски ожидания} SetCoimriMask(FHandle, 0);
End;
End;
End;
Результат наших изменений легко увидеть, запустив программы приема и передачи данных и программу Менеджера ресурсов. Загрузка процессора не превышает 1% независимо от наличия данных!
Глава 13
Специальные коммуникационные функции
Существует несколько специальных коммуникационных функций, используемых при реализации He-RS-232-протоколов. Эти функции пригодятся нам при работе с протоколом RS-485. Кроме того, Windows NT/2000 предоставляет еще несколько функций для асинхронной работы с портами. В этой главе мы рассмотрим их использование.
Выполнение дополнительных операций
Функция EscapeCommFunction позволяет выполнить дополнительные операции. Эта функция посылает код напрямую драйверу. Например, приложение может останавливать передачу с помощью посылки кода setbreak и возобновлять ее с помощью кода clrbreak. Те же операции могут быть выполнены С ПОМОЩЬЮ функций SetCommBreak И ClearCommBreak.
Функция EscapeCommFunction может использоваться для рСсШизации ручного управления сигналами модема. Например, с помощью кодов clrdtr и setdtr можно вручную управлять линией DTR (листинг 13.1). Заметим, что для ручного управления этими линиями необходима установка соответствующего режима В ПОЛЯХ fDtrControl И fRtsControl блока DCB(СМ. гл. 20).
Листинг 13.1. Управление линиями DTR и RTS
TComPort = class (TComponent)
Public
{Управление линией DTR}
Procedure ToggleDTR(State : Boolean);
{Управление линией RTS}
Procedure ToggleRTS(State : Boolean);
End;
{Управление линией DTR}
Procedure TComPort.ToggleDTR(State : Boolean);
const Funes: Array[Boolean] of Cardinal = (CLRDTR, SETDTR); Begin
If Connected then
EscapeCommFunction(FHandle, Funes[State]);
End;
{Управление линией RTS}
Procedure TComPort.ToggleRTS(State : Boolean);
const Funes: Array[Boolean] of Cardinal = (CLRRTS, SETRTS); Begin
If Connected then
EscapeCommFunction(FHandle, Funes[State]);
End;
Кроме описанных в гл. 21 кодов функций, в Windows 95/98 присутствует еще одна полезная, но недокументированная в MSDN функция с кодом 10. Ее вызов возвращает базовый адрес порта. Пример использования этой функции приведен в листинге 13.2.
Листинг 13.2. Недокументированнь|й вызов ЕscapeCommFuncШоп — базовый адрес порта (только в Windows 95/98)
TComPort = class(TComponent)
Public
Function GetBaseAddress : Word;
End;
{отображение базового адреса} procedure TForml.btnBaseAdrClick(Sender: TObject); var S : String;
begin
S:= IntToHex(FPort.GetBaseAddress, 3);
MessageDlg(базовый адрес порта '+S, mt Information, [mbOK], 0); end;
{получение базового адреса}
Function TComPort. GetBaseAddress : Word;
Begin
{ недокументированный код функции — получение базового адреса в dx}
{ работает только для Windows 95/98 }
EscapeCommFunction (FHandle, 10);
Asm
mov Result, dx
End;
End;
Прямое управление драйвером
Функция DeviceioControi позволяет посылать расширенные команды управления напрямую драйверу ресурса, указывая ему выполнить определенные действия. Эта функция позволяет специфически конфигурировать драйвер, устанавливая характеристики, нужные устройству.
Мы уже использовали эту функцию в гл. 9 для обращения к расширенному драйверу GivelO. В этой главе мы рассмотрим использование данной функции для обращения к стандартному драйверу последовательного порта.
Важно!
Возможность обращения к драйверу последовательного порта появилась только в Windows 2000.
Как мы видели, при вызове функции DeviceioConroi в нее передаются код выполняемой операции, буфер входных параметров и буфер для выходных параметров. Коды операций и формат буферов для драйвера последовательного порта описаны в справочной части книги, в гл. 22
Сначала мы сравним прямое обращение к драйверу и обычный метод доступа, а затем приступим к написанию настоящей программы. Для этого добавим в наш класс TComPort два метода, позволяющих считать и установить скорость обмена порта, которые будут использовать DeviceioControi, как показано в листинге 13.3.
Листинг 13.3. Использование DeviceioControi (Windows 2000)
unit ComPort;
interface
uses
Windows, SysUtils, Classes, Forms;
TComPort = class(TComponent)
Public {Дополнительные функции}
Procedure Direct_SetBaudRate(Baud : Word);
Function Direct_GetBaudRate : Cardinal;
End;
{Константы для формирования функций}
{коммуникационного драйвера}
Const
FILE_DEVICE_SERIAL_PORT = $0000001b;
{Константы METHOD_xxx. Для СОМ-портов}
{необходимо использовать METHOD_BUFF'ERED} Const
METHOD_BUFFERED = 0;
METHOD. IN_D 工 RECT = 1;
METHOD_OUT_D工RECT = 2;
METHOD_NEITHER = 3;
{Метод доступа. Для COM-портов нужно} {использовать FILE__ANY_ACCESS } Const
FILE_ANY_?kCCESS = $0000;
FILE_READ_ACCESS = $0001;
FILE_WRITE_ACCESS = $0002;
{Формирование кода функции DeviceloControl} {для обращения к коммуникационному порту } function Get_Ctl_Code(Nr: Integer): Integer; begin
Result:=
(FILE_DEVICE_SERIAL_PORT shl 16) or (FILE_ANY_ACCESS shl 14) or
(Nr shl 2) or
METHOD_BUFFERED; end;
{Функция получения скорости обмена}
{с помощью прямого обращения к драйверу}
Function TComPort. Direct_GetBaudRate : Cardinal;
Var IOCTL_SERIAL_GET_BAUD_RATE, Skip : Cardinal; Begin
Result := 0;
{Код функции 工。CTL—SER工AL_GET_BAUD_ RATE = 20} I0CTL一SER工AL_GET_BAUD_RATE:= Get_Ctl_Code (20);
{Обращаемся к драйверу. Если успешно, результат}
{будет в переменной Result}
If not DeviceloControl(FHandle, IOCTL__SERIAL_GET_BAUD_RATE, nil, 0, @Result, SizeOf(Result), Skip, nil) then
Begin
MessageDlg(* Ошибка •+IntToStr(GetLastError), mtError, [mbOK], 0);
End;
End;
{Функция установки скорости обмена}
{с помощью прямого обращения к драйверу}
Procedure TComPort. Direct_SetBaudRate (Baud : Word);
Var IOCTL_SERIAL_SET_BAUD_E^ATE, Skip : Cardinal;
Begin
{Код функции IOCTL_SERIAL_SET_BAUD_RATE = 01} IOCTL_SERIAL_SET_BAUD_FtATE: = Get_Ctl_Code (01);
{Обращаемся к драйверу}
DeviceloControl (FHandle, IOCTL_SERIAL_SET_BAUD_FIATE, ©Baud, SizeOf (Baud), nil, 0, Skip, nil);
End;
Добавив к нашей программе еще одну кнопку, например, Получить скорость обмена, мы вызовем нашу новую функцию (листинг 13.4).
;Листинг 13.4. Вызов Direct_GetBaudRate (Windows 2000)
procedure TForml.btnTestlClick(Sender: TObject);
begin
MessageDlg(
Format(* Скорость o6MeHa=%d*, [ FPort.Direct_GetBaudRate],
mt Con firmat ion, [rnbOK], 0
); end;
Если сообщение отобразит правильную скорость обмена, значит, все прошло без ошибок.
Теперь посмотрим, какие преимущества дает прямое обращение к драйверу устройства. Очевидно, если мы напрямую обращаемся к драйверу, то экономим процессорное время. Убедиться в этом нам поможет небольшой тест, приведенный в листинге 13.5.
;Листинг 13.5. Сравнение GetCommStatе и DiviceloControl (Windows 2000)
Procedure TComPort.Test;
Var deb: TDCB; i : Longint; Tl, T2 : Cardinal;
IOCTL_SERIAL_GET_BAUD_RATE, Skip, Rate : Cardinal;
Begin
{Инициализация deb}
FillChar(deb, SizeOf(deb), 0);
deb.DCBLength:= SizeOf(deb);
{Вызываем GetCoinmState 5000 раз} T1:= GetTickCount;
For i:= 1 to 5000 do {-156} GetCommState(FHandle, deb);
T2:= GetTickCount;
{Печатаем затраченное время}
MessageDlg(IntToStr(T2-T1), mtWarning, [mbCKl, 0);
{Получаем код функции}
IOCTL__SERIAL_GET_BAUD_RATE: = Get_Ctl_Code (20);
{Вызываем DeviсеIoContго1 5000 раз}
T1:= GetTickCount;{-30}
For i:= 1 to 5000 do
DeviceioControl (FHandle, IOCTL_SERIAL_GET_BAUD__RATE, nil, 0, ©Rate, SizeOf(Rate), Skip, nil);
T2:= GetTickCount;
{Печатаем затраченное время}
MessageDlg (IntToStr (T2-T1) # mtWarning, [mbOKl, 0);
End;
В этом примере мы выполняем одну и ту же функцию —— определение скорости обмена — двумя способами. Первый способ — обычный вызов Getcommstate, а второй — прямое обращение к порту. Запустив эту программу на компьютере с процессором Р4-2500 и Windows 2000, мы получили весьма впечатляющий результат. Затраты на первый цикл составляют примерно 140—160 мс, а на второй 20—35 мс. Прямое обращение быстрее обычного как минимум в 4 раза!
Теперь МЫ ГОТОВЫ К практическому использованию функции DeviceioControl. Модуль, показанный в листинге 136 работает с портом в режиме on-line, позволяя менять настройки и управлять линиями порта. В отличие от примеров предыдущей главы, этот модуль не хранит текущее состояние настроек порта.
[Листинг 13.6« Модуль ComPortOnline (Windows 2000)
unit ComPortOnline;
interface
uses Windows, SysUtils, Classes, Forms;
Type
TByteSize =( bs5, bs6, bs7, bs8 );
TParity =( ptNONE, ptODD, ptEVEN, ptMARK, ptSPACE );
TStopbits =
sblBITS, sblHALFBITS, sb2BITS
);
TComPortOnline = class(TComponent)
Protected
FBaseAddressFHandle FByteSizeFStopbitsFBaseAddressFHandle FByteSizeFStopbits:Word;:THandle;:TByteSize;:TStopbits;:Word;:THandle;:TByteSize;:TStopbits;{базовый адрес порта} {дескриптор порта} {число бит в байте} {число стоп-бит}{базовый адрес порта} {дескриптор порта} {число бит в байте} {число стоп-бит}
FBaseAddress
FHandle FByteSize
FStopbits
FBaseAddress
FHandle FByteSize
FStopbits
:Word;
:THandle;
:TByteSize;
:TStopbits;
:Word;
:THandle;
:TByteSize;
:TStopbits;
{базовый адрес порта} {дескриптор порта} {число бит в байте} {число стоп-бит}
{базовый адрес порта} {дескриптор порта} {число бит в байте} {число стоп-бит}
Procedure DoOpenPort; {открытие порта}
ProcedureProcedureDoClosePort;DoClosePort;{закрытие порта}{закрытие порта}
Procedure
Procedure
DoClosePort;
DoClosePort;
{закрытие порта}
{закрытие порта}
Private
FComNumber: Integer;
function GetConnected: Boolean;
procedure SetComNumber(const Value: Integer); procedure SetBaudRate(const Value: Cardinal); function GetBaudRate : Cardinal;
function GetLineControl(const Index: Integer): Byte;
procedure SetLineControl(const Index: Integer; const Value: Byte);
function GetByteSize: TByteSize;
function GetParity: TParity;
function GetStopbits: TStopbits;
procedure SetByteSize(const Value: TByteSize);
procedure SetParity(const Value: TParity);
procedure SetStopbits(const Value: TStopbits); function GetDTRRTS(const Index: Integer): Boolean;
procedure SetLTRRTS(const Index: Integer; const Value: Boolean);
function GetModemState(const Index: Integer): Boolean; procedure SetModemState(const Index: Integer;
const Value: Boolean);
Public
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
Public
{Открывает/закрывает порт} Procedure Open;
Procedure Close;
Function GetTest : Cardinal;
Public
Property Connected : Boolean read GetConnected;
{Номер порта. При изменении порт переоткрывается,} {если был открыт}
Property ComNumber : Integer read FCoroNuwber write SetCoinNuiriber stored False;
{Скорость обмена}
Property BaudRate : Cardinal read GetBaudRate write SetBaudRate stored False;
{Число бит в байте}
Property ByteSize : TByteSize read GetByteSize write SetByteSize stored False;
{Четность}
Property Parity : TParity read GetParity write SetParity stored False;
{Число стоп-бит}
Property Stopbits : TStopbits read GetStopbits
write SetStopbits stored False;
{Управление линией DTR}
Property DTR
:Boolean
index 0 read GetDTRRTS
write SetDTRRTS
stored False;
{Управление линией
DTR}
Property RTS
:Boolean
index
1 read
GetDTRRTS
write
SetDTRRTS
storec
i False;
{Управление линией
0UT1}
Property 0UT1
:Boolean
index
0 read
GetModemState
write SetModemState stored False;
{Управление линией 0UT2}
Property OUT2: Boolean index 1 read GetModemState
write SetModemState stored False;
End;
implement at ion
uses Dialogs;
Type
TSERIAL_LINE_CONTROL = packed record StopBits : Byte; Parity : Byte; WordLength : Byte;
End;
{Константы для GetCtrlCode}
Const
FILE_DEVICE_SERIAL_PORT 二 $0000001b;
METHOD_BUFFERED= 0;
FILE_ANY_ACCESS= $0000;
function GetCtrlCode(NFunction : Integer): Integer; begin
Result:=
(FILE_DEVICE_SERIAL_PORT shl 16) or
(FILE_ANY_?kCCESS shl 14) or
(NFunction shl 2) or METHOD_BUFFERED; end;
Function SetByteMask(Value : Cardinal; Mask : Byte; State : Boolean): Cardinal;
Begin
If State then begin
Result:= Value or Mask;
End else begin
Result:= Value and ($FF~Mask);
End;
End;
{Конструктор}
Constructor TComPortOnline. Create (AOwner : TComponent);
Begin
Inherited Create (AOwner);
FHandle := INVALID_HANDLE_VALUE;
FCcmNwnber := -1;
End;
{Деструктор}
Destructor TComPortOnl ine. Destroy;
Begin
DoClosePort;
Inherited Destroy;
End;
{Возвращает состояние соединения}
function TComPortOnline.GetConnected: Boolean;
begin
Result:= (FHandle <> INVALID_HANDLE_VALUE); end;
{Открытие порта}
Procedure TComPortOnline. Open;
Begin
DoOpenPort;
End;
{Закрытие порта}
Procedure TComPortOnline.Close;
Begin
DoClosePort;
End;
{Установка номера порта}
procedure TComPortOnline. SetConiNuinber (const Value: Integer);
var Active : Boolean;
begin
Active:= Connected; {сохраним значение активности порта}
If Active then DoClosePort; {закрыть порт перед изменением индекса} FCoiriNumber: = Value; {устанавливаем новое значение номера порта} If Active then DoOpenPort; {открыть порт, если он был закрыт} end;
{открытие порта}
Procedure TComPortOnline.DoOpenPort;
Var ComName : String;
Begin
If Connected then Exit;
{Для портов 1—9 можно использовать простые имена ССМ1—СОМ9, } {но для портов 10-256 надо писать полное имя. Для общности ) {будем всегда исполь з-овать полное имя порта}
ComName: = Format {* \ \. \COM%-d *, [FComNumber]);
{Открытие последовательнего порта }
FHandle:= CreateFiie{
PChar(ComName), {передаем имя открываемого порта} GENERJC_READ or GENERIC_WRITE, {ресурс для чтения и записи} 0, { неразделяемый ресурс } nil, { Нет атрибутов защиты }
OPEN_EXISTING, {вернуть ошибку, если ресурс не существует}
FILE_ATTR工BUTE—NORMAL or FILE_FLAG_OVERLAPPED, {асинхронный режим доступа}
0 { Должно быть 0 для СОМ-портов }
);
End;
{закрытие порта}
Procedure TComPorrOnline.DoClosePort;
Begin
If not Connected then Exit;
{Освобождение дескриптора порта}
CloseHandle (FHandle);
{Сброс дескриптора порта}
FHandle:= INVALI D_HANDLE_VALUE;
End;
{— Get/Set BaudRate}
function TComPortOnline.GetBaudRate : Cardinal;
Var Skip : Cardinal;
begin
Result: = 0;
Device IoCont го1(FHandle, GetCtrlCode(20)y nil, 0, @Result, SizeOf(Result), Skip, nil); end;
procedure TComPortOnline.SetBaudRate(const Value: Cardinal);
Var Skip : Cardinal;
begin
Device I oControl (FHandle, GetCtrlCode (1) , @ Value, SizeOf (Value), nil, 0r Skip, nil);
end;
function TComPortOnline.GetLineControl(const Index: Integer): Byte;
var L : TSERIAL_LINE_CONTROL; Skip : Cardinal;
begin
DeviceIoControl(FHandle, GetCtrlCode(21), nil, 0, @L, SizeOf(L), Skip, nil);
Case Index of
Result:= L.WordLength;
Result:= L.Parity;
Result:= L.StopBits;
End;
end;
procedure TComPortOnline.SetLineControl(const Index: Integer; const Value: Byte);
var L : TSERIAL_LINE_CONTROL; Skip : Cardinal;
begin
DeviceloContго1(FHandle, GetCtrlCode(21), nil, 0, @L, SizeOf(L)r Skip, nil);
Case Index of
0: L.WordLength:= Value;
L.Parity := Value;
L.StopBits := Value;
End;
DeviceloControl(FHandle, GetCtrlCode(3), @L, SizeOf(L), nil, 0, Skip, nil);
end;
function TComPortOnline.GetByteSize: TByteSize;
begin
Result:= TByteSize(GetLineControl(0)- 5); end;
function TComPortOnline.GetParity: TParity;
begin
Result:= TParity(GetLineControl(1));
end;
function TComPortOnline.GetStopbits: TStopbits;
begin
Result:= TStopbits(GetLineControl(2)); end;
procedure TComPortOnline.SetByteSize(const Value: TByteSize);
begin
SetLineControl(0, Byte(Value)+5); end;
procedure TComPortOnline.SetParity(const Value: TParity);
begin
SetLineControl(1, Byte(Value));
end;
procedure TComPortOnline.SetStopbits(const Value: TStopbits); begin
SetLineControl (2, Byte (Value));
end;
function TComPortOnline.GetDTRRTS(const Index: Integer): Boolean; var _DTRRST, Skip : Cardinal;
begin
DeviceloControl(FHandle, GetCtrlCode(30), nil, 0, @_DTRRST, SizeOf(_DTRRST), Skip, nil);
Case Index of
0: Result:= (_DTRRST and 1) <> 0; // DTR
Result:= (_DTRRST and 2) <> 0; // RTS
End;
end;
procedure TComPortOnline.SetDTRRTS(const Index: Integer; const Value: Boolean);
var _DTRRST, Skip : Cardinal; Code : Integer;
begin
If Value then begin Case Index of
Code:= 9; // 工OCTL—SER工AL_SET_DTR
Code:= 12; // 工OCTL—SER工AL_SET_RTS
End;
End else begin Case Index of
Code:= 10; // 工OCTL—SER工AL_CLR_DTR
Code:= 13; // IOCTL_SERIAL_CLR_RTS
End;
End;
DeviceloControl(FHandle, GetCtrlCode(Code), nil, 0, nil, 0, Skip, nil);
end;
Function TComPortOnline.GetТеst : Cardinal;
var Skip : Cardinal;
begin
DeviceloControl(FHandle, GetCtrlCode(37), nil, 0, @Result, SizeOf(Result), Skip, nil);
end;
function TComPoгtOnline.GetModemState(const Index: Integer): Boolean; var _MStater Skip : Cardinal;
begin
DeviceloControl(FHandle, GetCtrlCode(37)r nil, 0,
@_MState, SizeOf(_MState), Skip, nil);
Case Index of
0: Result:= (_MState and 4) <> 0; // OUT1
1: Result: = (_MState and 8) <> 0; // 3T2
End;
end;
procedure TComPortOnline.SetModemState(const Index: Integer;
const Value: Boolean);
var _MState, Skip : Cardinal;
begin
DeviceloControl(FHandle, GetCtrlCode(37), nil, 0,
@_MState, SizeOf(_MState), Skip, nil);
Case Index of
0: SetByteMask(_MState, 4, Value); // OUT1
1: SetByteMask(_MState, 8, Value); // OUT2
End;
DeviceloControl(FHandle, GetCtrlCode(38),@_MState, SizeOf(_MStare), nil, 0, Skip, nil);
end;
end.
Как видно, обращение к свойствам порта не представляет большой сложности и сводится к простой установке свойств. Нам кажется, особых комментариев здесь не требуется. Отметим только, что чтение и установка свойств порта производятся напрямую, без сохранения текущего состояния, и, по этой причине, для всех свойств нашего нового класса выставлены спецификаторы stored: = False, означающие отключение сохранения значения этих свойств в DFM-файле.
Для тестирования нового модуля мы создадим небольшую программу, исходный код которой показан в листинге 13.7, а внешний вид формы — на рис. 13.1.
Рис_ 13.1. Окно программыпрямого обращения к драйверу порта
Листинг Ш7. Модуль CbmPortOnline (Windows 2000)
*. 品%金.:";#;…"""……;;.*…;;,了“……";“:"'》」..%^±»,备“***..,.**、〉T*;::也?.*%/……““、“…」;……",
unit Unitl;
interface
uses
Mask,Mask,SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics fontrolsr Forms, Dialogs, ExtCtrls, StdCtrls, Buttons, Spin, ComCtrls, Conportonl ine;
Mask,
Mask,
type
TForml = class (TForm)
Panel1: TPane1;
Panel2: TPane1;
btnClose: TBitBtn;
ParamPane1: TPanel;
rgPortIndex: TRadioGroup;
btnOpenPort: TSpeedButton; btnClosePort: TSpeedButton; StatusBar: TStatusBar;
GroupBox3: TGroupBox; BaudRate: TSpinEdit; btnBaudRate: TButton;
GroupBox4: TGroupBox;
Label1: TLabel; cbDataLen: TComboBox;
cbStopBits: TConiboBox;
Label2: TLabel; cbParity: TComboBox;
Labe113: TLabe1; btnLineProperties: TButton; GroupBox1: TGroupBox;
cbRTS: TCheckBox;
cbDTR: TCheckBox;
Timerl: TTimer;
_DTR: TRadioButton;
_RTS: TRadioButton; cbOUT1: TCheckBox;
cbOUT2: TCheckBox;
_OUT1: TRadioButton;
OUT2: TRadioButton;
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure privateprocedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure privateFormCreate(Sender: TObject); btnCloseClick(Sender: TObject); FormDestroy(Sender: TObject); btnOpenPortClick(Sender: TObject); btnClosePortClick(Sender: TObject); btnLinePropertiesClick(Sender: TObject); btnBaudRateClick(Sender: TObject); cbRTSClick(Sender: TObject); cbDTRClick(Sender: TObject); TimerITimer(Sender: TObject); cbOUTlClick(Sender: TObject);
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure private
procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure private
{ Компонент для доступа к порту. Будем создавать его}
{ динамически.
FPort : TComPortOnline;
public procedure SetEnabledCont rols;
end;
var
Forml: TForml;
implementat ion
[$R *.DFM}
{ Создание компонента доступа к портам при создании формы } procedure TForml. FormCreate (Sender: TObject);
begin
FPort:= TComPortOnline.Create(Self);
SetEnabledControls; end;
(Вызывается при уничтожении формы (выходе из программы) } procedure TForml. FormDestroy (Sender: TObject); begin
FPort. Free; end;
{Кнопка открытия порта} procedure TForml.btnOpenPortClick(Sender: TObject); begin
FPort.ConiNumber: = rgPortIndex. Itemlndex+l; FPort .Open;
If FPort.Connected then
StatusBar.Panels[0].Text:= 'Порт успешно открыт *
Else
StatusBar.Panels[0].Text:= *Ошибка открытия порта'; SetEnabledControls;
{Отображение текущего состояния порта} If FPort.Connected then begin cbDTR.Checked := FPort.DTR; cbRTS.Checked := FPort.RTS;
cbOUTl.Checked:= FPort.OUT1;
cbOUT2.Checked:= FPort.OUT2;
BaudRate.Value:= FPort.BaudRate;
cbDataLen.Itemlndex := Byte(FPort.ByteSize); cbStopBits.Itemindex:= Byte(FPort.Stopbits); cbParity.Itemindex := Byte(FPort.Parity ); End;
end;
procedure TForml•btnClosePortClick(Sender: TObject); begin
FPort.Close;
StatusBar.Panels[0].Text:= * *;
SerEnabledControls;
end;
procedure TForml.SetEnabledCont го1s;
Var Active : Boolean;
begin
Active:= FPort.Connected;
btnClosePort.Enabled
=Act i ve;
rgPortIndex.Enabled btnOpenPort.Enabled cbDataLen.Enabled
=not Active;
=not Active;
=Active;
cbStopBits.Enabled cbParity.Enabled BaudRate.Enabled
=Active;
=Active;
=Active;
btnBaudRate.Enabled end;
=Active;
{Кнопка закрытия програъ^мы} procedure TForml.btnCloseClick(Sender: TObject); begin
Close;
end;
{Установка базового адреса} procedure TForml.btnBaudRateClick(Sender: TObject); begin
FPort. BaudRate: = BaudRate. Value; end;
{Установка характеристик обмена}
procedure TForml.btnLinePropertiesClick(Sender: TObject); begin
FPort.Parity:= TParity(cbParity.Itemindex);
FPort.ByteSize:= TByteSize(cbDataLen.Itemindex);
Case cbStopBits.Itemindex of 0: // 1 стоп-бит
FPort.Stopbits:= sblBITS;
// 2 (1.5) стоп-бита
If c±>DataLen.Itemlndex = 0 then FPort.Stopbits:= sblHALFBITS Else FPort.Stopbits:= sb2BITS;
End;
end;
{Управление RTS)
procedure TForml.cbRTSClick(Sender: TObject); begin
FPort. RTS: = cbRT S. Che eked; end;
{Управление DTR}
procedure TForml. cbDTRClick (Sender: TObj ect); begin
FPort. DTR: = cbDTR. Checked; end;
procedure TForml. cbOUTIClick (Sender: TObj ect); begin
FPort. OUT 1: = cbOUT 1. Che eked; end;
procedure TForml. TimerlTimer (Sender: TObj ect); begin
If not FPort.Connected then Exit;
_RTS.Checked := FPort.RTS;
DTR.Checked := FPort.DTR;
_OUT1.Checked:= FPort.0UT1;
_OUT2.Checked:- FPort.OUT2; end;
end.
Обнаружение всех портов системы
Очень часто необходимо отобразить список всех портов, доступных в системе. Например, для выбора порта при подк.пючении устройства. Конечно, можно в цикле перебрать порты от COM I до СОМ256 и пробовать их открывать, но, во-первых, это просто неудобно, а во-вторых, порт может быть занят и не откроется, а программа воспримет это как отсутствие порта.
Для перечисления всех доступных портов используется функция EnumPorts. Хотя в документации она описывается как функция дня работы с портами принтера, она возвращает все порты, доступные в системе. Заметим только, что в Delphi для ее использования требуется подключить модуль winSpool.
Подробное описание параметров функции EnumPorts приведено в гл. 20, а в этой главе мы покажем пример ее использования.
Добавим к нашему классу TComPort еше два метода. Метод EnumCcmPorts будет возвращать нам список всех коммуникационных портов в системе, а EnumComPortsEx не только имена портов, но и их описания.
Код функций и пример их использования приведены в листинге 13.8. Нам потребуется также добавить на основную форму кнопку вызова функции и TListBox для отображения списка портов.
• ■ ^" W "»■•?,-•*•••••••-• * ^^ *!■?■* ' » * Г ^ J.^rs ' - - 5«Л| «J.T* " * " * » *;?:• ?" ?^,"-.* «.5 ? ' " "Л'.м ^ * "■ ?SV ''■*■? *"■,.,**..,、.,.♦.,
j Листинг 13.8. Перечисление всех СОМ-портов, доступных в системе
Х::;.・.《 S.工《:算.■“•““・為 W «...-...Л :“.,::.“•%疊;:・™二;..“.N.“&a』?*•—;““::* •:〉■ У*3, — * ;-;;♦工….……:岱
unit Unitl;
interface
{Перечислить доступные порты}
procedure TForml.btnEnumPortsClick(Sender: TObject); begin
{! Добавим на форглу TListBox для отображения списка} TComPort.EnumComPortsEx(ListBoxl.Iterns);
end;
unit ComPort;
interface
uses
Windows, SysUtils, Classes, Forms;
TComPort = class (TComponent)
Publiс {Дополнительные функции}
{Перечисление всех ССМ-портов}
Class procedure EnumComPorts(Ports: TStrings);
Class procedure EnumComPortsEx(Ports: TStrings); End;
implementation
uses
Dialogs, WinSpool;
{ перечисление имен всех доступных коммуникационных портов} class procedure TComPort.EnumCcmPorts(Ports: TStrings); var
BytesNeeded, Returned, I: DWORD;
Success: Boolean;
PortsPtr: Pointer;
InfoPtr: PPortlnfol;
TenpStr: string;
begin
{Запрос размера нужного буфера}
Success := EnumPorts(nil, 1, nil, 0, BytesNeeded, Returned);
If (not Success) and (GetLastError = ERR0R_INSUFFICIENT__BUFFER) then begin
{Отводим нужный блок памяти}
Getl^em (PortsPtг, BytesNeeded);
Try
{Получаем список имен портов}
Success := EnumPorts(nilr 1, PortsPtr, BytesNeeded, BytesNeeded, Returned);
{Переписываем имена в StringList, отсеивая не-ССМ-порты}
For 工:=0 to Returned — 1 do begin
InfoPtr := PPortInfol(DWORD(PortsPtr) + I * SizeOf(TPortlnfol));
TempStr := InfoPtr74.pName;
If Copy(Tempstr, 1, 3) = '€04,then
Ports .Add (TempStr);
End;
Finally
{Освобождаем буфер}
FreeMem(Ports Ptr);
End;
End;
end;
{ перечисление всех доступных коммуникационных портов и их описаний}
class procedure TComPort.EnumComPortsEx(Ports: TStrings); var
BytesNeeded, Returned, I: DWORD;
Success: Boolean;
PortsPtr: Pointer;
InfoPtr: PPortInfo2;
TempStr: string;
Description : string;
begin
{Запрос размера нужного буфера}
Success := EnumPorts(nil, 2, nil, 0, BytesNeeded, Returned);
If (not Success) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then begin
{Отводим нужный блок памяти}
GetMem(PortsPtr, BytesNeeded);
Try
{Получаем список имен портов и их описания}
Success := EnumPorts(nil, 2, PortsPtr, BytesNeeded, BytesNeeded, Returned);
{Переписываем имена в StringList, отсеивая не-ССМ-порты}
For I := 0 to Returned - 1 do begin
InfoPtr := PPortInfo2(DWORD(PortsPtr) + I * SizeOf(TPortInfo2));
TempStr : = InfoPtr74. pPortName;
{Добавляем описание порта, если оно есть}
Descriptions •';
If InfoPtr/'. pDescription <> nil then
Descriptions ' ' + InfoPtr74.pDescription;
{Переписываем имена в StringList, отсеивая не-ССМ~порты}
If Copy(TempStr, 1, 3) = *СОМ* then begin
TempStr:= TempStr + Description;
Ports.Add(TempStr);
End;
End;
Finally
{Освобождаем буфер}
FreeMem (Ports Ptr);
End;
end;
end;
Запустив программу и нажав кнопку отображения списка портов, мы получим, например, тако访 результат:
СОМ1: Последовательный порт
COM2: Последовательный порт
COM3: Genius GM56PC工-L Modem Card
Прежде чем перейти к следующему разделу, еще одно маленькое замечание относительно реализации новых функций. Мы описали эти функции как class procedure. Это позволяет вызывать их, не создавая экземпляр конкретного класса, Т. е. просто TComPort. EnumComPortsEx ().
Имена портов
В Windows NT/2000/XP устройства, такие, как диски, а также последовательные и параллельные порты, имеют внутренние имена и, необязательно одно или несколько внешних DOS-имен. Ядро и драйверы используют только внутренние имена, а прикладные программы Win32 используют DOS-имена.
Внутренние имена имеют формат поддиректории в папке Device, например, \Device\CDRomO ИЛИ \Device\SeralO. Однако прикладные программы не могут использовать внутренние имена и должны использовать внешние, такие как "А:”,”С:“,”СОМ1”,"LPT1”. Несколько DOS-имен могут указывать на одно и то же устройство, однако обратное утверждение не верно. Одно имя может ссылаться только на одно устройство. Так, например, последовательный порт \Device\SerialO может иметь имена СОМ! и МуСОМ.
Хотя прикладные программы не могут использовать внутренние имена, они могут получать, добавлять и удалять DOS-имена для внутренних имен устройств. Эти операции ВЫПОЛНЯЮТСЯ С ПОМОЩЬЮ функций QueryDosDevice II DefileDosDevice, которые описаны в гл. 21.
Код программы, демонстрирующей использование этих функций, приведен в листинге 13.9.
Листийг 13.9. Работа с ООЗ^именами устройств
unit Unitl;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, ExtCtrls;
type
TForml = class (TForm)
StatusBar: TStatusBar;
IbNameList: TListBox;
Pane11: TPanel;
Labe11: TLabe1;
Label2: TLabel;
NTDeviceName: TEdit;
DOSDeviceName: TEdit;
btnGetName: TButton;
btnAddName: TButton;
btnDelName: TButton;
btnGetList: TButton;
procedure btnGetNameClick(Sender: TObject); procedure btnAddNameClick(Sender: TObject);
procedure btnDelNameCLick(Sender: TObject);
procedure btnGetListClick(Sender: TObject); procedure IbNameListDblClick(Sender: TObject);
private
public
end;
var
Forml: TForml;
impl ement aticn
{$R * .dfm}
{Получение NT-имени no DOS-имени}
procedure TForml.btnGetNameClick(Sender: TObject);
Var Result : Array [ 1. .MAX_PATIi] of Char;
begin
If (Que ryDos Device(PChar(DOS DeviceName.Text), ©Result, MAX_PATH) <> 0) then
NtDeviceName.Text:= StrPas(@Result)
Else
NtDeviceName.Text:= 1Устройство не существует1;
end;
{Добавить DOS-имя для NT-имени}
procedure TForml.btnAddNameClick(Sender: TObject);
begin
If not DefineDosDevice(
DDD_RAW_TARGET_PATH,
PChar(DosDeviceName.Text),
FChar(NtDeviceName.Text))
then
StatusBar.PaneIs[0].Text:= 'Ошибка добавления имени';
end;
{Удалить DOS-ршя }
procedure TForml.btnDelNameClick(Sender: TObject);
Var Result : Array [1. .MAX PATH] of Char;
begin
{Ищем NT-имя для удаляемого DOS-имени}
If not(QueryDosDevice(PChar(DOSDeviceName.Text), ©Result, MAX_PATH) <> 0) then begin
StatusBar.Panels[0].Text:= 'DOS-имя не определено *;
Exit;
End;
{Удаляем DOS-имя}
If not De f ine Dos Devi ce (
DDD_RAW_TARGET__PATH or
DDD_REMOVE_DEFINITION or DDD_EXACT_MATCH_ON_REMOVE, PChar(DosDeviceName.Text), PChar(NtDeviceName.Text) )then
StatusBar.Panels[0].Text:= 'Ошибка удаления имени'; end;
(Получение всех имен устройства} procedure TForml.btnGetListClick(Sender: TObject);
var BufSize : Cardinal; P, PName : Pointer; SName : String; begin
(Очищаем предьщущий список} IbNameList.Items.Clear;
{Размер буфера} BufSize:= 10240;
{Распределяем память для буфера} GetMem(P, BufSize);
{Запрашиваем список имен}
If QueryDosDevice(nil, Р, BufSize) о 0 then begin
{Цикл по всем именам. . . }
PName:= Р;
While (True) do begin
SName:= StrPas(PName);
If SName == * * then Break;
{Добавляем в список}
IbNameList. Items. Add (SName);
{Переход к следующему устройству}
{Сдвигаем указатель на следующую строку}
PNarae:= Pointer (Longlnt (PName) + Length(SName) +1);
End;
End;
{Освоботкдаем буфер}
FreeMem(P);
end;
(Сортировка списка по двойному щелчку}
procedure TForml. IbNameListDblClick (Sender: TObj ect); begin
IbNaineList. Sorted: - True;
IbNameList. Sorted: = Fai se;
end;
end.
Рис. 13.2. Окно программы работы с именами устройств
Основное диалоговое окно нашей тестовой программы показано на рис. 13.2. Введем в верхнее поле строку СОМ1 и нажмем кнопку Получить NT-имя устройства по его DOS-имени. В поле NT-имя должна отобразиться строка, похожая на **\ Devi се \ Serial О'*.
Теперь введем в поле DOS-имя какое-нибудь новое имя, например, MyDevice и нажмем кнопку Добавить DOS-имя для NT-имени. Результат мы можем увидеть, нажав кнопку Получить полный список DOS-имен или Получить NT-имя устройства по его DOS-имени.
Следует отметить, что присвоение одному коммуникационному порту двух или более имен не дает возможности открывать этот порт из нескольких программ одновременно. Открыв порт С0М1 в одной программе, мы не сможем открыть порт MyDevice из npyroiL если они ссылаются на одно физическое устройство \Device\SerialO.
В Windows 2000 и выше имеется возможность напрямую задавать имя коммуникационного порта, точнее говоря, выбирать его номер. Это можно сделать в диалоговом окне Advanced Settings for СОМхг (Дополнительные установки .для порта СОМхх) окна Менеджера устройств (Device Manager). Вид диалогового окна показан на рис. 13.3.
?^^z^Actm Yew :?^^z^Actm Yew :一■—— ■二;"街面。缈一■—— ■二;"街面。缈里 Cwn^uler Managemeni (Local]-爲;System Tools+ J;吋 Event Viewef+ 为 System Information+ ■ Performance Loqs and Alerts* » J Shared Folders負 Device M田lagfer+ 缢 Local Users and Groups-3 Slorage3 Disk Management専 Disk DefragmenterО Logical Dnves•各 Removable Storage里 Cwn^uler Managemeni (Local]-爲;System Tools+ J;吋 Event Viewef+ 为 System Information+ ■ Performance Loqs and Alerts* » J Shared Folders負 Device M田lagfer+ 缢 Local Users and Groups-3 Slorage3 Disk Management専 Disk DefragmenterО Logical Dnves•各 Removable StorageT Acri*rahcneT Acri*rahcne_ & R/A♦型 Computer+,一I Disk drives+ 登 Display adapters+ 为 DVD/tD-ROM drives+ \J, Floppy disk contrdlerc+ gJ Floppy disk diives+ ◎ IDE ATAZATAPI conVollets* Y1 Keyboaids♦. C Mice and otbei pointing devicest> 宮 Monitorsi1 勲 Network adapters-! J Ports (COM & LPTj» Communications Port (C0M1J » Communications Port (COMZi_ & R/A♦型 Computer+,一I Disk drives+ 登 Display adapters+ 为 DVD/tD-ROM drives+ \J, Floppy disk contrdlerc+ gJ Floppy disk diives+ ◎ IDE ATAZATAPI conVollets* Y1 Keyboaids♦. C Mice and otbei pointing devicest> 宮 Monitorsi1 勲 Network adapters-! J Ports (COM & LPTj» Communications Port (C0M1J » Communications Port (COMZi.4Genethl PM Seifrigs | Driver | Reswrces J.4Genethl PM Seifrigs | Driver | Reswrces JDe^abits.De^abits.P«f触 J NoneP«f触 J NoneFlow cortrol J NoneFlow cortrol J Noneadvanced Settings for COM1advanced Settings for COM1Adveiced |Adveiced |Restae DeleulfjRestae Deleulfj
?^^z^
Actm Yew :
?^^z^
Actm Yew :
一■—— ■二;"街面。缈
一■—— ■二;"街面。缈
里 Cwn^uler Managemeni (Local]
-爲;System Tools
+ J;吋 Event Viewef
+ 为 System Information
+ ■ Performance Loqs and Alerts
* » J Shared Folders
負 Device M田lagfer
+ 缢 Local Users and Groups
-3 Slorage
3 Disk Management
専 Disk Defragmenter
О Logical Dnves
•各 Removable Storage
里 Cwn^uler Managemeni (Local]
-爲;System Tools
+ J;吋 Event Viewef
+ 为 System Information
+ ■ Performance Loqs and Alerts
* » J Shared Folders
負 Device M田lagfer
+ 缢 Local Users and Groups
-3 Slorage
3 Disk Management
専 Disk Defragmenter
О Logical Dnves
•各 Removable Storage
T Acri*rahcne
T Acri*rahcne
_ & R/A
♦型 Computer
+,一I Disk drives
+ 登 Display adapters
+ 为 DVD/tD-ROM drives
+ \J, Floppy disk contrdlerc
+ gJ Floppy disk diives
+ ◎ IDE ATAZATAPI conVollets
* Y1 Keyboaids
♦. C Mice and otbei pointing devices
t> 宮 Monitors
i1 勲 Network adapters
-! J Ports (COM & LPTj
» Communications Port (C0M1J » Communications Port (COMZi
_ & R/A
♦型 Computer
+,一I Disk drives
+ 登 Display adapters
+ 为 DVD/tD-ROM drives
+ \J, Floppy disk contrdlerc
+ gJ Floppy disk diives
+ ◎ IDE ATAZATAPI conVollets
* Y1 Keyboaids
♦. C Mice and otbei pointing devices
t> 宮 Monitors
i1 勲 Network adapters
-! J Ports (COM & LPTj
» Communications Port (C0M1J » Communications Port (COMZi
.4
Genethl PM Seifrigs | Driver | Reswrces J
.4
Genethl PM Seifrigs | Driver | Reswrces J
De^abits.
De^abits.
P«f触 J None
P«f触 J None
Flow cortrol J None
Flow cortrol J None
advanced Settings for COM1
advanced Settings for COM1
Adveiced |
Adveiced |
Restae Deleulfj
Restae Deleulfj
В U?e FIFO buffws (requires 16^3 comp确We UART]В U?e FIFO buffws (requires 16^3 comp确We UART]OKOK
В U?e FIFO buffws (requires 16^3 comp确We UART]
В U?e FIFO buffws (requires 16^3 comp确We UART]
OK
OK
Sriect lower settings to cawscf сот«^юп problems.Sdect hi^e« saltings fee faster peifotri)ance.Sriect lower settings to cawscf сот«^юп problems.Sdect hi^e« saltings fee faster peifotri)ance.Cancel ICancel IDefaultsDefaultsReceive Buf®Receive Buf®Lcwf1|Lcwf1|Hjgh(14)Hjgh(14)0 4)0 4)OKOKCanedCanedТгэп$тй ВЗ尿Тгэп$тй ВЗ尿Low⑴Low⑴J Hi^dB]J Hi^dB](16)(16)COM PotiNurobw:COM PotiNurobw:回Ml回Ml□□
Sriect lower settings to cawscf сот«^юп problems.
Sdect hi^e« saltings fee faster peifotri)ance.
Sriect lower settings to cawscf сот«^юп problems.
Sdect hi^e« saltings fee faster peifotri)ance.
Cancel I
Cancel I
Defaults
Defaults
Receive Buf®
Receive Buf®
Lcwf1|
Lcwf1|
Hjgh(14)
Hjgh(14)
0 4)
0 4)
OK
OK
Caned
Caned
Тгэп$тй ВЗ尿
Тгэп$тй ВЗ尿
Low⑴
Low⑴
J Hi^dB]
J Hi^dB]
(16)
(16)
COM PotiNurobw:
COM PotiNurobw:
回Ml
回Ml
□
□
Рис. 13.3. Выбор имени порта в Windows 2000
АРС-функции Windows 2000/ХР
Механизм асинхронного вызова процедур (АРС, Asynchronous Procedure Calls) позволяет задать адрес процедуры, которая будет вызываться системой при завершении асинхронной операции.
Подробное рассмотрение механизма и функций АРС выходит за рамки этой книги. Рассмотрим только использование трех "основных* функций —— ReadFileEx, WriteFileEx И SleepEx. Все АРС-фунКЦИИ MOiyr ИСПОЛЬЗОВИТЬСЯ только в Windows NT/2000/XP (они присутствуют и в Windows 98, но не могут использоваться для коммуникационных портов).
По принципу действия эти функции похожи на прерывания в DOS. Вызов этих функций стартует асинхронную операцию, а при завершении этой операции вызывается определенная (Ьункинх, адрес которой задается как параметр. Для того чтобы функция завершения была вызвана, необходимо, чтобы поток находился в так называемом 'тревожном состоянии*' (alertable wait state). Перевод потока в такое состояние производится с помощью вызова "расширенной" функции ожидания SleepEx, а также функциями WaitForSingleObjectEx И WaitForMultipleObjectsEx.
Разумеется, для использования этих функций порт должен быть открыт с помощью флага f i le_flag_o verlap ped.
В листинге 13.10 приводится код функций приема и передачи байта, использующих АРС-вызовы вместо обычных асинхронных функций. Полный код программ можно найти на прилагаемом компакт-диске.
Листинг 13.10. Использование АРС-функций Windows 2000
unit ComPort;
interface
uses
Windows, SysUtils, Classes, Forms;
Function TComPort.WriteByte(const В : Byte) : Boolean;
Var WriteOL : TOverLapped; {структура для асинхронной записи}
{Callback-процедура, вызываемая после завершения передачи}
Procedure OnCompletionWrite(dwErrorCode, dwNumberOfBytesTransfered : Cardinal; var IpOverlapped : TOverlapped );far; stdcall;
begin
MessageBeep(0);
end;
Begin
Result:= False;
{создание события для асинхронной записи)
FillChar(WriteOL, SizeOf(WriteOL), 0);
WriteOL.hEvent:= CreateEvent(nil, True, True, nil);
{асинхронная отправка байта}
WriteFileEx(FHandle, @B, 1, WriteOL, @OnCcxnpletionWrite);
SleepEx(INFINITE, True);
{освобождение дескриптора события)
CloseHandle(WriteOL.hEvent);
End;
{Основная функция потока}
Procedure TReadThread.Execute;
Var ReadOL : TOverLapped; {структура для асинхронного чтения}
{Са1Iback-npoueдура, вызывающаяся при получении байта}
Procedure OnCompletionRead(dwErrorCode, dwNxmberOfBytesTrans fered : Cardinal; var IpOverlapped : TOverlapped); far; stdcall;
begin
{К сожалению, не удалось вызвать Synchronize}
{из callback-функции, т. к. она выполняется)
{в рамках одного потока}
end;
Begin
With FOwner do
While (not Terminated) and Connected do begin {пока порт открыт}
{Запуск операции асинхронного чтения}
ReadFileEx(FHandle, @FBytez 1, ©ReadOL, 0OnCompletionRead);
{Ожидание завершения операции)
SleepEx(INFINITE, True);
{Сюда мы попадем, когда байт будет принят} Synchronize (DoReadByte);
End;
End;
{Вызывается для передачи события о приходе байта}
{в основной компонент через метод Synchronize )
Procedure TReadThread. DoReadByte;
Begin
With FOwner do begin
If Assigned (FOnReadByte) then
FPState:= FOwner.GetState(FErrCode);
FNState:= FOwner.GetModemState;
FOnReadByte{FByte, FPState, FErrCode, FMState);
End;
End;
Функции обратного вызова должны иметь спецификатор stdcaii. Заметим, что передачу байта основному потоку мы производим не в функции обратного вызова (хотя она и вызывается при получении байта), а в теле потока, т. к. вызвать synchronize и обращение к объектам другого потока из функции OnCompletionRead невозможно. И еще одно замечание. Система не использует параметр hEvent в структуре overlapped, передаваемой в функции writeFiieEx и ReadFileEx. Это поле можно использовать под свои нужды.
Глава 14
Реализация протоколов обмена
До сих пор мы принимали и передавали всего один байт. В реальной программе необходимо принимать и передавать значительно больше информации. В этой главе мы займемся реализацией различных протоколов обмена.
Прием и передача простого пакета
Как уже отмечалось, во всех предыдущих примерах мы принимали и передавали только один байт. В этом разделе мы научимся производить обмен целым буфером, а для проверки работоспособности нашей новой программы мы добавим методы приема и передачи строк.
Простой пакет данных ——это поеледовательность байтов, заканчивающаяся нулем. Для передачи пакета данных этого достаточно. А вот с приемом будет сложнее. Одним из параметров функции ReadFile является число байтов, которые мы хотим получить. В следующих главах такой величиной станет длина пакета данных, но пока мы просто добавим в класс TComPort, который мы использовали в гл. 12, свойство inBufSize. Это свойство будет задавать размер входной очереди.
Для передачи последовательности байтов мы заменим метод writeByte на writeBuffer, с помощью которого создадим дополнительную функцию writestring, удобную для проверки работоспособности программы.
Кроме того, нам потребуется добавить специальное свойство waitForFuiiBuffer, управляющее моментом чтения данных с порта. Если waitForFullBuffer будет равно True, мы будем читать данные с порта только при заполнении всего буфера (т. е. когда в буфере накопится не менее inBufSize байт), а если False, то чтение данных мы будем производить при получении хоть какого-нибудь количества байтов.
Для проверки приема и передачи буферов мы будем использовать прием и передачу строк. Для этого потребуется внести некоторые изменения в ос- новную форму, а именно:
удалим индикаторы состояния линии и молема (здесь они нам не потребуются);
в окне передачи данных заменим компонент TSpinEdit на TEdit, а в окне приема — компонент TEdit заменим на TListBox. Это позволит нам передавать вместо одного байта целую строку и отображать полученные строки;
口 кнопка X будет очищать весь буфер приема;
кнопка Отправить будет передавать целую строку;
口 событие OnReadByte необходимо заменить на OnReadPacket, ЧТО ПОЗВОЛИТ нам добавлять полученные строки в TListBox, который будет служить буфером приема.
Изменения, которые потребуется внести в класс TComPort, приведены в листинге 14.1. Полный код программы можно найти на прилагаемом компакт-диске.
Листинг 14,1 „Прием и передача строки символов. Модуль тсотРо亡七
unit ComPort;
interface
uses
Windows, SysUtils, Classes, Forms;
{тип события при получении байта}
TReadPacketEvent = procedure (const Packet : Pointer; const Size : Integer; const ErrCode: Cardinal) of object;
TComPort = class; {опережающее описание класса}
{читающий поток}
TReadThread = class (TThread)
FOwnerFOwner:TComPort;:TComPort;
FOwner
FOwner
:TComPort;
:TComPort;
FInBufferFInBuffer: Pointer; {входной буфер}: Pointer; {входной буфер}FErrCodeFErrCode: Cardinal;{сохраняет код ошибок}: Cardinal;{сохраняет код ошибок}FCountFCount:Integer; {реально прочитанное число байт}:Integer; {реально прочитанное число байт}ProtectedProtectedProcedureProcedureExecute; override;Execute; override;ProcedureProcedureDoReadPacket;DoReadPacket;
FInBuffer
FInBuffer
: Pointer; {входной буфер}
: Pointer; {входной буфер}
FErrCode
FErrCode
: Cardinal;{сохраняет код ошибок}
: Cardinal;{сохраняет код ошибок}
FCount
FCount
:Integer; {реально прочитанное число байт}
:Integer; {реально прочитанное число байт}
Protected
Protected
Procedure
Procedure
Execute; override;
Execute; override;
Procedure
Procedure
DoReadPacket;
DoReadPacket;
Public
Constructor Create(AOwner : TComPort);
Destructor Destroy; override;
End;
TComPort = class(TComponent) Protected
FInBufSize : Cardinal; {размер входной очереди}
FWaitFullBuffer : Boolean; {ожидание наполнения буфера} Public
{Размер входного буфера}
Property InBufSize : Cardinal read FInBufSize
write SetlnBufSize;
{Ожидание наполнения буфера}
Property WaitFullBuffer : Boolean read FWaitFullBuffer write FWaitFullBuffer;
Public
{Событие, вызываемое при получении байта}
Property OnReadPacket : TReadPacketEvent read FOnReadPacket write FOnReadPacket;
End;
Constructor TComPort.Create(AOwner : TComponent);
Begin
Inherited Create(AOwner);
FHandle: = INVALID_HANDLE__VALUE;
FReadThread:^ nil;
FInBufSize :- 10;
FWaitFullBuffer: = False;
End;
Destructor TComPort. Destroy;
Begin
{Закрываем порт и соединение}
DoClosePort;
Inherited Destroy;
End;
{Изменение размера входного буфера}
procedure TComPort.SetInBufSize (const Value: Cardinal);
begin
{Разрешаем менять только при выключенном соединении} If Connected then Exit;
{Нельзя задать нулевое или неверное значение размера}
If Value <= 0 then Exit;
{Запоминаем новый размер буфера}
FInBufSize:= Value;
end;
{Передача строки}
Function TComPort.WriteString(const S : String) : Boolean;
Begin
WriteBuffer (PChar (S), Length (S));
End;
{Передача буфера}
Function TComPort.WriteBuffer(const P : PChar; const Size : Integer): Boolean;
Var Signaled, RealWrite, BytesTrans : Cardinal;
WriteOL : TOverLapped; {структура для асинхронной записи}
Begin
Result := False;
If P = nil then Exit;
{создание события для а синхронной записи}
Fill Char (WriteOL, SizeOf (WriteOL), 0);
WriteOL.hEvent:= CreateEvent(nil. True, True, nil);
Try
{начало асинхронной записи}
WriteFile(FHandle, РЛ, Size, RealWrite, GWriteOL);
{ожидания завершения асинхронной операции}
Signaled:= WaitForSingleObject(WriteOL.hEvent, INFINITE);
{получение результата асинхронной операции}
Result :=
(Signaled = WAIT_OBJECT_0) and
(GetOverlappedResult(FHandle, WriteOL, BytesTrans, False)); Finally
{освобождение дескриптора события}
CloseHandle(WriteOL.hEvent);
End;
End;
{Класс TReadThread }
Constructor TReadThread.Create(AOwner : TComPort);
Begin
Inherited Create(True);
FOwner:= AOwner;
{Создаем новый буфер}
GetMem(FInBuffer, FOwner.FlnBufSize);
End;
Destructor TReadThread.Destroy;
Begin
{Освобождаем буфер}
FreeMem(FInBuffer);
Inherited Destroy;
End;
{Основная функция потока}
Procedure TReadThread.Execute;
Var В : Byte; Currentstate : TComStat;
AvaibleBytes, ErrCode, Modemstater RealRead : Cardinal;
ReadOL : TOverLapped; {структура для асинхронного чтения} Signaled, Mask : DWORD;
BytesTrans : DWORD; {не используется для WaitConunEvent} bReadable : Boolean; {готовность к чтению данных}
Begin
With FOwner do begin
Try
{создание события для асинхронного чтения}
FillChar(ReadOLr SizeOf(ReadOL), 0);
ReadOL.hEvent:= CreateEvent(nil, True, True, nil);
{Маска событий, которые будет отслеживать читающий поток } {Пока это только получение символа}
SеtCoinmMas k (FHand 1 e, EV_RXCHAR);
(пока порт открыт}
While (not Terminated) and Connected do begin { Ждем одного из событий }
WaitCowmEvent(FHandle, Mask, @ReadOL);
Signaled:= WaitForSingleObject(ReadOL.hEvent, INFINITE);
If (Signaled = WAIT_OBJECT_0) then begin If GetOverlappedResult(FHandle, ReadOL, BytesTrans, False) then begin
{после GetOverlappedResult в переменной mask,}
{которая передавалась в WaitCoiranEvent, появятся флаги}
{произошедших событий, либо 0 в случае ошибки.}
If (Mask and EV_RXCHAR) <> 0 then begin {Получаем состояние порта (линий и модема)} Currentstate:= GetState(ErrCode);
ModemState := GetModemState;
{ Число полученных, но еще не прочитанных байт} AvaibleBytes:= Currentstate.cblnQue;
{ Проверка числа доступных байт}
If FWaitFullBuffer then begin
{ждать только полного буфера}
bReadable:= AvaibleBytes >= FInBufSize;
End else begin
{ждать любого числа байт} bReadabIe:= AvaibleBytes > 0; End;
If bReadable then begin {Чистка буфера} ZeroMemory(FInBuffer, FInBufSize); If ReadFile (FHandle, FInBuffer74, Min(FInBufSize, AvaibleBytes), RealRead, @ReadOL) then begin {сохраняем параметры вызова события} FErrCode:= ErrCode;
FCount := RealRead;
{Вызываем событие OnReadByte. Для синхронизации с VCL} {надо вызвать метод Synchronize}
Synchronize(DoReadPacket);
End;
End;
End;
End;
End;
End;
Finally {закрытие дескриптора сигнального объекта} CloseHandle(ReadOL.hEvent);
{Сброс события и маски ожидания}
SetCommMask(FHandle, 0);
End;
End;
End;
{Вызывается для передачи события о приходе байта} {в основной компонент через метод Synchronize } Procedure TReadThread•DoReadPacket;
Begin
With FOwner do begin
If Assigned (OnReadPacket) then
OnReadPacket(FInBuffer, FCount, FErrCode);
End;
End;
end.
Перед созданием теста программы для нового модуля еще немного модифицируем сам модуль. Нам будет удобнее, если вместо модуля и динамического создания компонента доступа к порту мы будем использовать обычные компоненты Delphi.
Создание компонента
Прежде чем заняться созданием более сложных протоколов обмена, немного модифицируем наши программу и класс. В предыдущих главах мы строили примеры, исходя из аналогичных DOS-программ. В этой главе мы предлагаем окончательно избавиться от всего лишнего и значительно упростить наш класс. Во-первых, для редактирования свойств порта в Windows уже есть специальная функция commConfigDia丄од, поэтому нет необходимости использо- вать свои диалоговые окна (эта функция подробно описана в м. 27).
Во-вторых, до сих пор мы использовали динамическое создание нашего класса, в то время как Delphi предоставляет более удобный механизм —— компоненты.
Создадим компонент, основанный на TComPort. Для этого нам потребуется:
добавить класс тсотРгор, который будет хранить настройки нашего порта;
добавить метод, вызывающий диалоговое окно редактирования свойств порта;
добавить специальный редактор свойств порта из Инспектора объектов;
создать ресурс с иконкой нашего компонента для палитры компонентов;
П зарегистрировать наш класс в палитре компонентов.
Подробное описание процесса создания компонентов выходит за рамки этой книги. Всех интересующихся мы отсылаем к бестселлеру Рея Конопки [7]. Изменения в коде класса TComPort показаны в листинге 14.2, а полный код и файл ресурса ComPort.dcr можно найти на прилагаемом компакт-диске.
!f,^^•?^*•^<!■^^•"^,г,'7;" • • '• "•—у ♦•••'•>•■••♦• г**………•&:♦…丝...♦‘. -л...
Листинг 14.2. Компонент TComPort
unit ComPort;
interface
uses
Windows, SysUtils, Classes, Forms;
{Класс свойств порта }
TComProp = class(TPersistent)
Private
FBaudRate
FByteSize FParity FStopbits {возвращает
:TBaudRate;
:TByteSize;
:TParity;
:TStopbits;
структуру DCB
{скорость обмена (бод)) {число бит в байте} {четность}
{число стоп-бит}из свойств порта}
function GetDCB: TDCB;
{устанавливает свойства порта из структуры DCB} procedure SetDCB(const Value: TDCB);
Public
Property DCB : TDCB read GetDCB write SetDCB;
Published
TBaudRate read FBaudRate write FBaudRate;
TByteSize
read FByteSize
write
FByteSize;
TParity
read FParity
write
FParity;
TStopbits
read. FStopbits
write
FStopbits;
{Скорость обмена} Property BaudRate {Число бит в байте} Property ByteSize {Четность} Property Parity {Число стоп-бит} Property Stopbits End;
TComPort = class(TComponent)
Protected
FComProp : TComProp; {свойства порта}
Public
{Диалог настройки параметров порта} Function CommDialog : Boolean;
Published
{Номер порта}
Property ComNumber : Integer read FComNumber write SetConiNumber;
{Параметры порта}
Property ComProp : TComProp read FComProp write FComProp;
{Размер входного буфера}
Property InBufSize : Cardinal read FInBufSize write SetlnBufSize;
{Ожидание наполнения буфера}
Property WaitFullBuffer : Boolean read FWaitFullBuffer write FWaitFullBuffer;
Published
{Событие, вызываемое при получении байта}
Property OnReadPacket : TReadPacketEvent read FOnReadPacket write FOnReadPacket;
End;
implement at i on
uses
Dialogs, WinSpool, Math;
{Добавление ресурса с иконкой для палитры компонент}
($R *.dcr}
Constructor TComPort. Create (AOwner : TComponent); Begin
Inherited Create (AOwner);
FCoiriNumbe r: = 1;
{создание объекта, сохраняющего свойства порта} FComProp := TComProp.Create; {свойства порта} FHandle:=工 NVAL 工 D_HANDLE_VALUE;
FReadThread: = nil;
FInBufSize := 10;
FWaitFullBuf fer: = False;
End;
Destructor TComPort.Destroy;
Begin
{Закрываем порт и соединение} DoClosePort;
FComProp.Free;
Inherited Destroy;
End;
{Установка свойств из FComProp}
Procedure TComPort.Арр1yComSe11ings;
Begin
If not Connected then Exit;
{ Установить настройки порта согласно DCB } SetCommState(FHandle, FComProp.DCB);
End;
{Диалог настройки параметров порта}
Function TComPort. CoinmDialog : Boolean;
Var ComCfg : TConunConfig;
Begin
ZeroMemory(@ComCfg, SizeOf(TConunConfig));
ComCfg. dwSize: = SizeOf (TConimConfig);
ComCfg.dcb:= FComProp.DCB;
,ComCfg);,ComCfg);Result:= CommConfigDialog(PChar(GetComName(False)), 0
,ComCfg);
,ComCfg);
If Result then begin
FComProp.DCB:= ComCfg.dcb;
ApplyComSettings;
End;
End;
{ Класс TComProp }
function TComProp.GetDCB: TDCB;
begin
{ Чистка структуры }
ZeroMemory(@Result, SizeOf(TDCB));
{ Пеле DCBLength должно содержать размер структуры } Result.DCBLength:= SizeOf(TDCB);
{ Скорость обмена {бод) }
Result. BaudRate :二 WindowsBaudRates [FBaudRate];
{ Windows не поддерживает небинарный режим работы }
{ последовательных портов }
Resul t. Flags := dcb_Bina ry;
{ Число бит в байте }
Result.ByteSize := 5 + Ord(FByteSize);
{ Контроль четности }
Result.Parity:= Ord(FParity);
{ Число стоп-бит }
Result.StopBits := Ord(FStopbits);
end;
procedure TComProp.SetDCB(const Value: TDCB);
var i : TBaudRate;
begin
{скорость обмена (бод) }
FBaudRate: = brllO;
For i:= Low(WindowsBaudRates) to High(WindowsBaudRates) do begin
If Value.BaudRate = WindowsBaudRates[i] then begin
FBaudRate: = i;
Break;
End;
End;
{число бит в байте}
FByteSize:= TByteSize(Value.ByteSize-5);
{четность}
FParity := TParity (Value.Parity);
{число стоп-бит}
FStopbits:= TStopBits(Value.StopBits);
end;
end.
Для того чтобы свойства стали видны в Инспекторе объектов, нам потребовалось перенести их в секцию Published. Однако этого недостаточно. Инспектор объектов Delphi не знает, как редактировать свойство comProp. Для добавления возможности редактирования нестандартного свойства следует добавить и зарегистрировать специальный класс-редактор. Код такого редактора показан в листинге 14.3.
i Листинг 143 Редактор свойства ComFrop для компонента TCamPoxt
Unit ComPortEditor;
interface
{$IFDEF VER140}
uses Windows, Classes, Graphics, Forms, Controls, Buttons, Designlntf, DesignWindows, StdCtrls, ComCtrls, DesignEditors, Typlnfo, SysUtils;
{$ELSE}
{$IFDEF VER150}
uses Windows, Classes, Graphics, Forms, Controls, Buttons, StdCtrls, ComCtrls, Typlnfo, SysUtils, DesignEditors, Designlntf;
{$ELSE}
uses Windows, Classes, Graphics, Forms, Controls, Buttons, StdCtrls, ComCtrls, Typlnfo, SysUtils, dsgnintf;
{$END工F}
{$END工F}
type
TDCBPropertyEditor = class(TClassProperty)
public
procedure Edit; override;
function GetAttributes: TPropertyAttributes; override;
function GetValue: string; override;
procedure Setvalue(const Value: string); override; end;
procedure Register;
implementation
Uses Dialogs, ComPort;
{ Отображение редактора }
procedure TDCBPropertyEditor.Edit;
Var Comp : TPersistent;
begin
{ Получить редактируемый компонент } Сопр:= Get Component (0);
{ Проверим тип компонента для исключения ошибок } If not (Corap is TComPort) then Exit;
If TComPort(Comp).CommDialog then
If Designer <> nil then {передаем уведомление об изменениях} Designer.Modified; {дизайнеру форм} end;
{ Параметры редактора свойств: }
{ pa Dia log— для редактирования будет отображать ся диалог}
{ paReadOnly — нельзя редактировать свойство напрямую}
function TDCBPropertyEditor.GetAttributes: TPropertyAttributes; begin Result := [paDialog, paSubProperties];
end;
(Строка, отображаемая в Инспекторе объектов} function TDCBPropertyEditor.GetValue: string; begin
Result := ' (COM Properties) *; end;
{ Установка значения) procedure TDCBPropertyEditor.SetValue(const Value: string); begin
{нельзя устанавливать значение} end;
{ Регистрация редактора свойств } procedure Register;
begin
RegisterComponents (' Comm', [TComPort ]);
RegisterPropertyEditor(Typeinfo(TComProp), TComPort, 'Comprop*, TDCBPropertyEditor); end; end.
Немного сложная конструкция условной компиляции нужна для того, чтобы мы могли использовать наш компонент в любой версии Delphi (3—6). Для создания редактора нам потребовалось перекрыть всего 4 метода класса TdassProperty. Их работу легко понять из комментариев к коду или открыв справку по Open Tools API.
Теперь все готово. Зарегистрировать новый компонент можно либо выбрав в меню Component пункт Install Component, либо открыв файл пакета, приведенный в листинге 14.4. Отметим, что при создании нового пакета в Delphi 6 необходимо вручную добавить модуль designide В секцию requires.
( Листинг 14*4. Пакет ComPortPacket, регистрирующий компонент TComPort
;и его редакторь! свойств (Delphi 6)
「.二二:.—;..:.丄;ff~ * . _::::!#;-\~1 J;;二:£;..
package ComPortPacket;
{$R *.res} {$AL工GN 8} {$ASSERT工ONS ON} {$BOOLEVAL OFF} {$DEBUGINFO ON} {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$IOCHECKS ON} {$LOCALSYMBOLS ON} {$LONGSTRINGS ON} {$OPENSTRINGS ON} {$OPTIMIZATION ON} {$OVERFLOWCHECKS OFF} {$RANGECHECKS OFF} {$REFERENCEINFO ON} {$SAFEDIVIDE OFF} {$STACKFRAMES OFF} {$TYPEDADDRESS OFF} {$VARSTR工NGCHECKS ON} {$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $400000} {$DESCRIPTION 1 Communication Components 1} {$IMPLICITBUILD OFF}
requires
rtl,
designide, {добавить после создания пакета для Delphi 6!} vcl;
contains
CcmPort in ' ComPort. pas',
ComPortEditor in *ComPortEditor.pas *;
end.
После установки значок TComPort должен появиться в палитре компонентов на вкладке Comm. Теперь нам остается заменить динамическое создание нашего класса на созданный компонент. Кроме того, как мы уже отмечали, мы удалим все лишние диалоговые окна настройки свойств порта, редактирование которых мы будем производить с помощью системной функции Windows.
Рис. 14.1. Форма, использующая компонент TComPort
На рис. 14.1 показан новый вид формы и Инспектор объектов, отображающий свойства нашего компонента, а листинг 14.5 показывает исходный текст программы.
£g■株科二紀兇•-:■忽"г :-依::?м■北禁1 父.:"w " "■?無 ±”S?"*6®:"1'",.?!?**-ту*■♦;•*"*,- " " T -吋:тр:':,"- 中""■•■-■ -*":学,
I Листинг 14,5. Использование компонента TComPort ::v , ■';»*^ . ■_ -•二 VYj-、"-W - '• ■二… 步 *-■•'-- •* --'--
unit Unitl;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Buttons, Spin, Mask, ComPort, ComCtrIs;
type
TForml = class(TForm)
Pane11: TPane1;
Panel2: TPane1;
btnClose: TBitBtn; ParamPanel: TPanel; rgPortIndex: TRadioGroup; GroupBoxl: TGroupBox; bRead: TSpeedButton; btnClearReadBuf: TSpeedButton; GroupBox2: TGroupBox; btnSend: TSpeedButton; btnOpenPort: TSpeedButton; btnClosePort: TSpeedButton; StatusBar: TStatusBar; ESendStr: TEdit;
IbBufRead: TListBox;
FPort: TComPort;
btnComProperty: TSpeedButton;
procedure procedure procedure procedureprocedure procedure procedure procedureFormCreate(Sender: TObject); btnCloseClick(Sender: TObject); bReadClick(Sender: TObject); btnSendClick(Sender: TObject);
procedure procedure procedure procedure
procedure procedure procedure procedure
procedure btnClearReadBufClick(Sender: TObject);
procedure btnOpenPortClick(Sender: TObject);
procedure btnClosePortClick(Sender: TObject);
procedure FPortReadPacket(const Packet: Pointer; const Size: Integer; const ErrCode: Cardinal);
procedure btnComPropertyClick(Sender: TObject);
procedure rgPortlndexClick(Sender: TObject);
private
public
procedure SetEnabledControls;
end;
var
Forml: TForml;
implementation
{$R *.DFM}
{ Создание компонента доступа к портам при создании формы }
procedure TForml • FormCreate (Sender: TObject);
begin
SetEnabledControls;
end;
{Изменение номера порта}
procedure TForml. rgPortlndexClick(Sender: TObject);
begin
FPort. ComNumber: = rgPort Index. Itemlndex+l;
end;
{Кнопка открытия порта}
procedure TForml.btnOpenPortClick (Sender: TObject);
begin
FPort. Close;
FPort.CoxnNumber:- rgPortlndex• 11emlndex+1;
FPort. Open;
If FPort.Connected then
StatusBar.Panels[0].Text:= 'Порт успешно открыт *
Else
StatusBar.Panels[0].Text:= 'Ошибка открытия порта1;
SetEnabledControls;
end;
procedure TForml.btnClosePortClick(Sender: TObject);
begin
FPort.Close;
StatusBar.PaneIs[0].Text:= '1;
SetEnabledControls;
bRead.Down:= False;
end;
procedure TForml.SetEnabledControls;
Var Active : Boolean;
begin
Active:= FPort.Connected;
btnClosePort.Enabled:= Active;
rgPortlndex.Enabled := not Active;
btnOpenPort.Enabled := not Active;
btnComProperty.Enabled:= Active;
btnSend.Enabled:= Active; end;
{Кнопка закрытия программы}
procedure TForml.btnCloseClick(Sender: TObject);
begin
Close;
end;
{Кнопка опроса порта}
procedure TForml.bReadClick(Sender: TObject);
begin
FPort.ReadActive:= bRead.Down;
end;
procedure TForml.FPortReadPacket(const Packet: Pointer; const Size: Integer; const ErrCode: Cardinal);
Var S : String; i : Integer;
begin
S:= IntToStr (Size) +,;
For i:= 0 to Size-1 do begin
S:= S + Char(Pointer(Longlnt(Packet)+i)л);
End;
If ErrCode <> 0 then S:= * [ ?]'十 S;
IbBufRead. Items.Add(S);
end;
(Очистить буфер чтения}
procedure TForml.btnClearReadBufClick(Sender: TObject); begin
IbBufRead. Items .Clear;
end;
{Отправка байта}
procedure TForml.btnSendClick(Sender: TObject);
begin
FPort. Writ eSt ring (ESendStr .Text);
end;
(Настройка порта}
procedure TForml.btnComPropertyClick (Sender: TObject);
begin
FPort. CommDialog;
end;
end.
Как всегда, работоспособность получившейся программы можно проверить, запустив две копии программы для двух портов (рис. 14.2),
Рис. 14.2. Прием и передача строк
Реализация ASCII-протокола
Практически каждое устройство, подключаемое к коммуникационному порту, имеет свой уникальный протокол обмена. Изменять код классов TComPort и TReadThread для реализации различных протоколов обмена, конечно же, нерационально.
В этой главе мы создадим еще один компонент TComAscii, который будет "подключаться'* К компоненту TComPort и, пользуясь его свойствами, принимать и передавать данные, согласно некоторому ASC11-протоколу (что такое ASC 11 - протокол, можно прочитать в гл. 2).
Основная идея TcomAscii проста: программе не надо знать, какие реальные пакеты данных были получены, не надо знать и про пакеты, полученные с ошибками. Достаточно, если основная программа будет получать только проверенное содержимое этих пакетов. Более того, во многих случаях не нужно даже накапливать полученные пакеты — основной программе достаточно информации, содержащейся в последнем пакете (например, если передается текущее значение температуры или давления).
Исходный код компонента TComAscii приведен в листинге 14.6, а файл ресурса ComAscii.dcr, содержащий иконку для палитры компонентов, можно найти на прилагаемом компакт-диске.
IF 彳4・6・ К0МО0нент TCcxnAscii .出公m…三 一力二:二3:£2.一丁工-二[葬」二一二「
unit ComAscii;
interface
uses
Windowsz SysUtils, Classes, Forms, ComPort;
Type
(Событие при получении новой строки)
TNewStrRead = procedure(const NewStr : String) of object;
{Реализация ASCII-протокола обмена}
TComAscii = class (TComponent)
FComPort : TComPort;
private
FStartStr: String;
FEndStr : String;
FMaxLen : Word;
FOnNewStrRead : TNewStrRead;
FInBuf : String;
FStart : Boolean;
procedure SetComPort(const Value: TComPort);
procedure OnPortReadPacket(const Packet: Pointer;
const Size: Integer; const ErrCode: Cardinal);
function CheckStartStr : Boolean;
function CheckEndStr(var S : String) : Boolean;
procedure CheckMaxLen;
Public
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
Procedure Notification(ACoroponent: TComponent; Operation: TOperation); override;
Published
{Привязка к компоненту TComPort}
Property ComPort : TComPort read FComPort write SetComPort;
{Стартовая строка}
Property StartStr: String read FStartStr write FStartStr;
{Завершающая строка}
Property EndStr : String read FEndStr write FEndStr;
{Максимальная длина принимаемой строки}
Property MaxLen : Word read FMaxLen write FMaxLen;
Published
{Событие при получении новой строки}
Property OnNewStrRead : TNewStrRead read FOnNewStrRead write FOnNewStrRead;
Public
{Передача строки согласно протоколу }
Procedure SendString(const S : String);
End;
Procedure Register;
inpleinentation
{Добавление ресурса с иконкой для палитры компонентов}
{$R *.dcr}
Procedure Register;
Begin
RegisterComponents (1comm', [TComAsciiJ);
End;
Constructor TCoinAscii.Create(AOwner : TComponent);
Begin
Inherited Create(AOwner);
FInBuf :='';
FStart := False;
FMaxLen:= 10;
End;
Destructor TComAscii.Destroy; Begin
Inherited Destroy;
End;
{Вызывается при операциях в редакторе форм Delphi}
procedure TComAscii.Notification (AComponent: TComponent;
Operation: TOperation);
begin
inherited;
{Если компонент TComPort удалили с формы. . .}
if (Operation = opRemove) and (FComPort <> nil) then begin if AComponent = FComPort then FComPort: == nil;
end;
end;
{Передача строки согласно протоколу}
procedure TComAscii. SendString (const S: String);
begin
If Assigned(FComPort) then
If FComPort.Connected then
FComPort.WriteString(FStartStr + S + FEndStr);
end;
[Привязка к компоненту TComPort}
procedure TComAscii. SetComPort (const Value: TComPort);
begin
FComPort: = nil;
If Value <> nil then begin
FComPort: = Value;
{Заставляем компонент TComPort}
{пересылать уведомления редактора}
{форм этому компоненту}
FComPort.FreeNotification(Self);
{Перехватываем обработчик получения новых пакетов}
FComPort.OnReadPacket:= OnPortReadPacker;
End;
end;
{Обработка получения новой строки}
procedure TComAscii.OnPortReadPacket(const Packet: Pointer; const Size: Integer; const ErrCode: Cardinal);
Var S : String; i : Integer; begin
{Получаем из буфера строку}
S:='';
For i:= 0 to Size-1 do begin
S:= S + Char(Pointer(Longlnt(Packet)+i)^);
End;
{Сохраняем строку во входном буфере} FInBuf:= FInBuf + S;
{Если начало посылки еще не получено...}
If not FStart then begin
{Проверяем на наличие символов начала посылки}
If not CheckStartStr then begin CheckMaxLen;
Exit;
End;
{начало посылки найдено и выкинуто...} FStart:= True;
End;
{Если начало посылки уже получено...}
If FStart then begin
{Проверяем наличие символов конца пакета}
S:= 1f;
If not CheckEndStr(S) then begin CheckMaxLen;
Exit;
End;
{конец посылки найден и обработан...}
If Assigned(FOnNewStrRead) then FOnNewStrRead(S);
FStart:= False;
End;
end;
{Проверка на присутствие символов начала посылки } {Все символы до символов начала посылки удаляются} {Если начало посылки найдено, возвращает True }
function TComAscii.CheckStartStr : Boolean;
Var i : Integer;
begin
Result:= False;
i:= Pos(FStartStr, FInBuf);
If i = 0 then Exit; {в строке нет начала посылки} {удаляем все символы вместе с началом посылки} Delete(FInBuf, 1, i + Length(FStartStr)-1);
Result:= True; end;
{Ограничение входного буфера} procedure TComAscii.CheckMaxLen; var i : Integer;
begin
i: = Length(FInBuf);
If i > FMaxLen*2 then
Delete(FInBuf, 1, i - FMaxLen*2);
end;
{Проверка на присутствие символов конца посылки } {Если конец посылки найден, возвращает True } function TComAscii.CheckEndStr(var S : String) : Boolean; Var i : Integer; begin Result:= False;
i:= Pos(FEndStr, FInBuf);
If i = 0 then Exit; {в строке нет конца посылки} {получение ,,чиcтoй,, строки) S:= Copy(FInBuf, 1, i-1);
{удаляем все символы вместе с концом посылки}
Delete(FInBuf, 1, i + Length(FStartStг));
Result:= True;
end;
end.
После инсталляции в палитре компонентов должен появиться второй компонент.
Протокол передачи, который мы реализовали, заключается в добавлении к каждому передаваемому блоку данных префикса (задается свойством startstr) и постфикса (задается свойством Endstr). Естественно, символы, содержащиеся в добавочных строках, не должны встречаться внутри самого сообщения.
Как видно из кода, компонент перехватывает обработку onReadPacket и обрабатывает его самостоятельно. Для привязки компонента TComAscii к компоненту работы с портом необходимо в редакторе форм установить свойство ComPort в FPort, как показано на рис. 14.3. Основной программе достаточно обработать событие onNewStrRead, вызывающееся при получении полного пакета данных, и метод Sendstring для передачи данных. Всю остальную работу берет на себя компонент.
Пример использования компонента TComAscii показан в листинге 14.7, а полный исходный код содержится на прилагаемом компакт-диске.
Рис. 14,3. Привязка компонента TComAscii к компоненту TComPort
г¥:¥*****|?!^*<"・个"T:耳Т《::专安一::—,3嘗¥?"""?"^^坤”*""t""・整£1*3*”:"“а・".**":.・::f・"■.■,"""":" нг 14.7. ЙсцоЖзсгвание компонента тсоп^5dH 区型編£&bh's*瑞3sS: «j^iк瓦^^>*J«</!v^V-. ;L*ww^rv>S:^^>v;»Lwv^^^".;d f•■;<■ ..*...Tpl.:..:■『.》“.u彳
unit Unitl;
interface
uses
Controls,Controls,SysUtils, WinTypes, WinProcs; Messages, Classes, Graphics, Forms, Dialogs, ExtCtrls, StdCtrls, Buttons, Spin, Mask,
Controls,
Controls,
ComPort, ComCtrls, ComAscii;
type
TForml - class (TForm)
FPort: TComPort;
CamAscii: TComAscii;
procedure ComAsciiNewStrRead(const NewStr: String);
end;
Forml: TForml;
implementa t ion
{$R *.DFM}
{инициализация состояния кнопок}
procedure TForml. FormCreate (Sender: TObject); begin
Se tEnabledControls;
end;
{Отправка байта}
procedure TForml.btnSendclick(Sender: TObject);
begin
ComAscii.SendString(ESendStr.Text);
end;
{Получение новой строки}
procedure TForml.ComAsciiNewStrRead(const NewStr: String);
begin
IbBufRead. Items .Add (NewStr);
end;
end.
Теперь мы можем создать несколько компонентов для работы с разными протоколами. Подключая эти компоненты к TComPort, можно использовать разные протоколы, изменяя их одним щелчком мыши! Такая система напоминает связку DataSource-DataSet ДЛЯ баз данных.
Реализация передачи других данных также существенно упрощается. Например, листинг 14.8 показывает передачу числа с плавающей точкой.
{Передача числа с плавающей точкой}
{PointPos — заданная точность }
Procedure TComAscii.SendSingle(const S : Single; const PointPos : Integer);
Begin
SendString(Format(*%.*f*, [PointPos, S]));
End;
Реализация бинарного протокола
Как мы уже отмечали, строковый протокол обмена имеет несколько недостатков. Самый существенный из них ——большой объем данных, необходимых для передачи чисел, особенно чисел с плавающей точкой. Действительно, в примере из предыдущего раздела для передачи числа с двумя знаками до точки и четырьмя после точки потребуется (3+ 2+1+4 +3) =13 знаков. Таким образом, для передачи 4 байт информации требуется 13 байт. А при добавлении контрольной суммы потребуется еще 3 дополнительных байта.
В этой главе мы реализуем простейший бинарный протокол обмена, состоящий из 7 байт:
□ 0-й байт — стартовый байт;
О 1—4-й байты — число с плавающей точкой;
О 5-й байт ——завершающий байт;
О 6-й байт ——контрольная сумма.
Легко посчитать, что для передачи SINGLE-числа в бинарном виде потребуется всего (1 + 4 + 1) = 6 байт, или 7 — при передаче с контрольной суммой. Таким образом, мы выигрываем в объеме данных почти в два раза (причем независимо от числа знаков после точки)!
Так же как и в случае ASCII-протокола, мы создадим специальный компонент TComBin, который будет привязываться к транспортному компоненту TComPort. Исходный код этого компонента показан в листинге 14.9, а файл и ресурс с иконкой для палитры компонентов можно найти на прилагаемом компакт-диске.
Листинг 14.9. Компонент бинарного протокола обмена тс<жв£п
unit ConiBin;
interface
uses
Windows, SysUtils, Classes, Forms, Comport;
Type
{Событие при получении нового значения}
TNewValueRead = procedure(const Value : Single) of object;
SingleArray = Array [1. .4] of Byte;
{Реализация простого бинарного протокола обмена}
TComBin = с 1 a s s (TCorrponent)
private
FComPort : TComPort; {транспортный компонент} FStartByte : Byte;{байт начала посылки}
FEndByte : Byte;{байт конца посылки}
FOnNewValueRead : TNewValueRead; {событие} FByteIndex : Integer; {счетчик байт посылки}
FInBuffer : SingleArray; {буфер для числа}
FUseCS : Boolean; {проверка контрольной суммы} procedure SetComPort(const Value: TComPort); procedure OnPorrReadPacket(const Packet: Pointer; const Size: Integer; const ErrCode: Cardinal); function GetPacketsize : Integer;
{Вычисление контрольной суммы для числа Single} function GetCSSingle(AValue : Single) : Byte;
{Вычисление контроль ной суммы для SingleArray} function GetCSBuf(Buf : SingleArray) : Byte;
Public
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
Procedure Notification (ACoroponent: TCorrponent;
Operation: TOperation); override;
Published
{Привязка к компоненту TComPort}
Property ComPort : TComPort read FComPort write SetComPort;
{Стартовый байт}
Property StarrByte: Byte read FStartByte write FStartByte;
{Завершающий байт}
Property EndByte : Byte read FEndByte write FEndByte;
{Проверка контрольной суммы}
Property UseCS : Boolean read FUseCS write FUseCS; Published
{Событие при получении новой строки}
Property OnNewValueRead : TNewValueRead read FOnNewValueRead write FOnNewValueRead;
Public
{Передача числа согласно протоколу }
Procedure SendValue(const AValue : Single);
End;
Procedure Register;
irrplementation
{Добавление ресурса с иконкой для палитры компонентов} [$R *.dcr}
Procedure Register;
Begin
RegisterCorr^onents (* Coinm*, [TCoinBin]);
End;
Constructor TCoinBin.Create (AOwner : TCorrponent);
Begin
Inherited Create (AOwner);
FBytelndex: = 0;
End;
Destructor TCoinBin. Destroy;
Begin
Inherited Destroy;
End;
{Вызывается при операциях в редакторе форм Delphi}
procedure TCoinBin.Notification (ACorr^onent: TCorr^onent;
Operation: TOperation);
begin
inherited;
{Если компонент TComPort удалили с формы. . . }
if (Operation = opRemove) and (FComPort <> nil) then begin if AComponent = FComPort then FComPort:= nil;
end;
end;
{Вычисление контроль ной суммы для числа Single}
function TCoinBin. GetCSSingle (AValue : Single) : Byte;
Var Buf : SingleArray;
begin
Move (AValue, Buf, SizeOf (Buf));
Result: = GetCSBuf (Buf);
end;
{Вычисление контрольной суммы для SingleArray}
function TCoinBin.GetCSBuf (Buf : SingleArray) : Byte;
var i : Integer;
begin
Result:= 0;
{$R-}
For i:= Low(Buf) to High(Buf) do begin Result:= Result + Buf[i];
End;
{$R+} end;
{Передача числа согласно протоколу}
procedure TComBin.SendValue(const Walue : Single);
Var CS : Byte;
begin
If Assigned (FComPort) then
If FComPort.Connected then begin
{Послать символ начала посылки}
FComPort.WriteBuffer(©FStartByte, SizeOf(FStartByte));
{Послать число}
FComPort.WriteBuffer(@AValue, SizeOf(AValue ));
{Послать символ завершения посылки}
FComPort.WriteBuffer(@FEndByte, SizeOf(FEndByte ));
{контрольная сумма, если включено}
If FUseCS then begin
CS:= GetCSSingle(AValue);
FComPort.WriteBuffer(@CS, SizeOf(CS));
End;
End;
end;
{Привязка к компоненту TComPort}
procedure TComBin.SetComPort(const Value: TComPort); begin
FComPort:= nil;
If Value <> nil then begin
FComPort:- Value;
{Заставляем компонент TComPort}
{пересылать уведомления редактора}
{форм этому компоненту}
FComPort.FreeNotification(Self);
{Перехватываем обработчик получения новых пакетов} FComPort.OnReadPacket:= OnPortReadPacket;
End;
end;
{Вычисление размера передаваемого пакета} function TComBin. Get Packet Size : Integer; begin
Result:= SizeOf(FStartByte) + SizeOf(Single) + S i zeOf(FEndByte); If FUseCS then {+контрольная сумма}
Inc (Result);
end;
{Обработка получения нового значения}
procedure TComBin.OnPortReadPacket(const Packet: Pointer;
const Size: Integer; const ErrCode: Cardinal);
Var В : Byte; i : Integer; bNextByte : Boolean; AValue : Single; begin
For i:= 0 to Size-1 do begin
B:= Byte(Pointer(Longlnt(Packet)+i)л);
bNextByte:= False;
Case FBytelndex of
0: begin // ожидается символ начала посылки bNextByte:= (В = FStartByte);
End;{0}
.4: begin // получение числа FInBuffer[FBytelndex]:= В; bNextByte:= True;
End; {1. . 4}
begin // ожидается символ конца посылки bNextByte:= (В = FEndByte);
End; {5}
begin bNextByte:= (B= GetCSBuf(FInBuffer));
End; {6}
End;
If bNextByte then begin {переходим к ожиданию следующего} {байта посылки}
Inc(FByteIndex);
End else begin
{неверный формат посылки}
{начинаем ожидание сначала}
FByteIndex:= 0;
End;
{посылка закончена}
If FByteIndex - GetPacketSize then begin
Try
Try
{Переписываем буфер в число Single}
Move(FInBuffer, AValue, SizeOf(AValue));
Except End;
{Вызываем обработчик}
If Assigned(FOnNewValueRead) then
FOnNewValueRead(AValue);
Finally
{Независимо от ошибок начинаем отсчет снова}
FByteIndex:= 0;
End;
End;
End;
end;
end.
Основная работа производится В обработчике OnPortReadPacket. Рассмотрим его более подробно.
При запуске программы счетчик FByteindex равен 0, и, следовательно, компонент будет ожидать получения стартового байта. Счетчик байтов увеличится только в случае совпадения очередного байта со значением стартового байта. Затем, пока счетчик будет равен значению от 1 до 4, мы будем просто сохранять байты в буфер. Значение счетчика 5 будет означать, что компонент ожидает получения завершающего байта. Если хотя бы один байт не будет совпадать с протоколом обмена, переменная bNextByte примет значение False, и счетчик байтов сбросится в ноль. Завершение обработки пакета
произойдет в случае достижения счетчиком величины длины пакета. В этом случае мы скопируем полученные байты из временного буфера в переменную типа SINGLE И вызовем обработчик события OnNewValueRead.
Отдельно стоит обратить внимание на свойство компонента usees. Если usees установлено в True, обмен будет производиться с добавлением контрольной суммы. Контрольная сумма вычисляется В функции GetCSSingle и представляет собой байтовую сумму с переполнением всех байтов числа. Естественно, при добавлении контрольной суммы мы увеличиваем значение длины протокола, возвращаемое функцией GetPacketsize, на 1.
Теперь нужно либо добавить этот компонент в пакет ComPortPacket.dpk, либо создать другой пакет, как показано в листинге 14.10. После инсталляции пакета в палитре компонентов появится значок компонента TComBin.
Листинг 14.10. Пакет для установки компонента тсошВхп
蠱1.:“.. /:..熏;…上;:.;..& 厶….:;.¥“....* 謬; ;;j 觀;二….…二 …鎊二.;
package ComPortPacket;
{$R *. res} {$ALIGN 8} {$ASSERTIONS ON} {$B00LEVAL OFF} {$DEBUGINFO ON} {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$I0CHECKS ON} {$LCTALSYMBOLS ON} {$LONGSTR工NGS ON} {$OPENSTRINGS ON} 《OPTIMIZATION ON} {$OVERFLOWCHECKS OFF} {$RANGECHECKS OFF} {$REFERENCEINFO ON} {$SAFEDIVIDE OFF} {5STACKFRAMES OFF} {$TYPEDADDRESS OFF} {$VARSTR工NGCHECKS ON} {$WRITEABLECONST OFF} {SMINENUMSIZE 1} {$IMAGEBASE $400000}
{$ DESCRIPTION * Coinwnication Components * } {$IMPLICITBUILD OFF}
requires
rtl,
designide,
vcl;
contains
ComPort in 'ComPort.pas*, ComPortEditor in 'ComPortEditor.pas', ComBin in 'CowBin.pas';
end.
Рис. 14.4. Форма с компонентом ТComBin
Основная форма потребует мелких изменений. Во-первых, компонент TComAscii НуЖНО заменить на TComBin. Во-вторых, нужно создать новый
обработчик события, а метод передачи строки заменить передачей числа. Кроме того, мы добавили переключатель использования контрольной суммы при обмене. Измененная форма показана на рис. 14.4, а изменения в коде приведены в листинге 14.11. Полный исходный код можно найти на прилагаемом компакт-диске.
一,荷林胃病一,荷林胃病
一,荷林胃病
一,荷林胃病
unit Unitl;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Buttons, Spin, Mask,
ComPort, ComCt r 1 s, ComBin;
type
TForml = class (TForm)
FPort: TComPort;
ComBin: TConiBin; cbUseCS: TCheckBox;
procedure cbUseCSClick(Sender: TObject);
procedure ComBinNewValueRead(const Value: Single);
end;
var
Forml: TForml;
implementation
{$R *.DFM}
{Передача числа с плавающей точкой}
procedure TForml.btnSendClick (Sender: TObject);
Var V : Single;
begin
Try
V:= StrToFloat(ESendValue.Text);
Except
MessageDlg(Неверный формат числа1, mtError, [mbOK], 0);
Exit;
End;
ComBin. SendValue (V);
end;
{Получение нового числа}
procedure TForml.ComBinNewStrRead(const Value: Single);
begin
IbBufRead.Items.Add(FloatToStr(Value));
end;
(Включить использование контрольной суммы}
procedure TForml.cbUseCSClick(Sender: TObject);
begin
ComBin.UseCS:= cbUseCS.Checked;
end;
end.
Открыв одну копию программы для передачи по СОМ1, а вторую — для приема по COM2, можно поэкспериментировать с контрольной суммой. Естественно, что передача с контрольной суммой будет правильно приниматься даже при выключенной опции при приеме. Действительно, дополнительный байт будет считаться ошибочной посылкой и игнорироваться. А вот установить опции наоборот ——не получится: программа приема будет ожидать контрольной суммы, и если она не будет передана, то ни один пакет не будет получен.
Подводя итог, подчеркнем главный результат ——основная программа получает только правильные, освобожденные от "неинформационных* байтов, пакеты.
Глава 15
Вычисление контрольных сумм
В г/?. 2 мы уже обсуждали вопросы, связанные с применением контрольных сумм для повышения достоверности передаваемой информации. В этой главе мы рассмотрим различные способы вычисления контрольной суммы, применяемые в наиболее рас простране иных протоколах обмена.
Вычисление простой контрольной суммы
Самая простая контрольная сумма может быть вычислена при помощи суммирования с переполнением всех информационных байтов. Язык С не контролирует выход за пределы типа, поэтому реализация вычисления такой суммы сводится к обыкновенному суммированию всех байтов данных.
Реализация подсчета контрольной суммы в Pascal и Delphi показана в листинге 15.1. Естественно, в начале вычисления переменная cs должна быть инициализирована нулем.
Листинг 151 Вычисление простейшей контрольной суммы
byte { контрольная сумма } CS : Byte;
{В Pascal для вычисления контрольной суммы } {нужно отключать контроль переполнения } procedure AddCS (b : Byte);
begin
{$“}
CS:= CS + b;
{$R+} end;
{Б Delphi тоже можно отключать переполнение,} {а можно использовать механизм исключений } procedure AddCS(b : Byte);
begin
Try
CS:= CS + b;
Except End;
end;
С помощью дополнительной проверки можно обойтись без обработки исключений (листинг 15.2). Такая конструкция удобна при отладке программы, т. к. даже при включенной опции Останавливать при появлении исключений (Stop On Delphi Exception) программа не будет останавливаться на каждом байте, добавляемом к контрольной сумме.
;Листинг 15.2. Вымисление простой контрольной суммы без вызова ;исключения
{А с помощью if можно обойтись без обработки} {исключений}
procedure AddCS(b : byte); begin if (CS + b) > 256 then CS:= CS + b - 256 else
CS:= CS 十 b;
end;
Вычисление LCR
Собственно сам алгоритм вычисления LCR уже описан в 乙支 2, в разд. "Контрольная сумма LCR”, и нам остается только строго следовать его шагам. В листинге 15.3 приведен код вычисления LCR для буфера, задаваемого указателем р.
丁:.:…….售.聚….出翠审『У.…然 …"
Листинг 15.3. Вычисление контрольной суммы LCR
Function CalculateLRC(P : PChar; Len : Word) : Byte; Var i : Word;
Begin
{$R-}
Result := 0;
For i:= 0 to Len-1 do
Result:= Result + Byte(Pointer(Longlnt(P)+i)A);
Result:= $FF - Result;
Inc (Result);
{$R+}
End;
Следует отметить, что шаги 2 и 3 алгоритма (вычитание SFF и прибавление I ) сводятся к операции Result:= -Result, поэтому вычисление можно сократить (листинг 15.4).
^Листинг 15・4・. Вычисление контрольной суммы LCR
Function CalculateLRC (Р : PChar; Len : Word) : Byte;
Var i : Word;
Begin
{$R-}
Result: = 0;
For i:= 0 to Len-1 do
Result: = Result + Byte(Pointer(Longlnt(P)+i)^);
Result: = -Result;
{$R+}
End;
Вычисление CRC16
Алгоритм вычисления CRC16 также уже был описан в гл. 2, в разд. пКонтрольная сумма LCR”. Реализация этого алгоритма приведена в листинге 15.5, где показан код вычисления CRC16 для буфера, задаваемого указателем р.
Листинг 15.5. Вычисление контрольной суммы CRC16
R”♦… ……•-S$“”Zz,”・”*・S、、・、l<**»C・l“,””・>l・“・. ■♦“::;;♦■♦”■"】♦■<«.“■•■.*;、■••■♦★.■-..■■• •';♦♦♦”.■“■.”.?■■«*-**»
Function CalculateCRC16 (P : PChar; Len : Word) : Word;
Var iByte, i : Word; В : Byte;
Begin
{$R7
{инициализация}
Result:= $FFFF;
{цикл по всем байтам буфера}
For iByte:= 0 to Len-1 do begin
{очередной байт буфера}
B:= Byte(Pointer(Longlnt(P)+iByte)八);
{вычисление очередного CRC}
Result:= (Result and $FF00) + (B xor Lo(Result));
For i:= 1 to 8 do begin
If ((Result and $0001) <> 0) then
Result:= (Result shr 1) xor $A001
Else
Result:= (Result shr 1);
End;
End;
{$R+}
End;
Часто для увеличения скорости расчета создают таблицу всех возможных комбинаций CRC для старшего и младшего регистров, обеспечивая быстрое вычисление новой величины CRC для каждого байта буфера. Пример такого кода показан в листинге 15.6. Именно такой способ вычисления применяется в протоколе MODBUS.
:Листинг 15.6. Быстрое вычисление контрольной суммы CRC16
Const
CRCLoTable : Array [0..255] of Byte =(
$00,$Cl,$81,$40,$01,$C0,$80,541,$01,SC0,S80,S41,$00, $Cl,$81,$40,$01,$C0,$80,$41,$00,SCl,$81,$40,$00,$Cl, $81,S40,$01,SC0,$80,$41,$01,$C0,$80,$41,$00,$Cl,$81, $40,$00,$С1,$81,$40,$01,$С0,$80,$41,$00,$С1,$81,$40, $01/$C0,$80,$41,$01,$C0,$80,$41,$00,$Cl,$81,$40/$01, $C0/$80/$41,$00,$Cl,$81,$40/$00,$Cl,$81,$40,$01z$C0,
$80, $41, $00, $C1, $81, $40, SOI, SCO, $80, $41, SOI, $8, $80, $22,$00,$Cl,$81,$40,$00,$Cl,$81,$40,$01,$C0,$80,$41,
$01,$C0,$80,$41,$00,$Cl,$81,$40,$01,$C0,$80,$41,$00, $Cl,$81,$40,$00,$Cl,$81,$40,$01,$C0,$80,$41,$01,$C0, $80,$41,$00,$C1,$81,$40,$00,$C1,$81,$40,$01,SCO,$80, $41,$00,$С1,$81,$40,$01,$С0,$80,$41,$01,$С0,$80,$41,
$00,$С1,$81,$40,$00,$С1,$81,$40,$01,$СО,$80,$41,$01, $С0,$8О,$41,$ОО,$С1,$81,$4О,$О1,$СО,$8О,$41,$ОО,$С1, $81,$40/$00,$С1,$81,$40,$01,$С0,$80/$41,$00,$С1,$81, $40,$01,$С0,$80,$41/$00,$С0,$80/$41,$00,$С1,$81,$40, $01,$СО,$80,$41Г$00,$С1,$81,$40,$00,$С1,$81,$40,$01, $С0,$8О,$41,$О1,$СО,$8О,$41,$ОО,$С1,$81,$4О,$ОО,$С1, $81,$40,$01,$С0,$80,$41/$00,$С1,$81,$40,$01,$С0,$80, $41,$01,$С0,$80,$41,$00,$С1,$81,$40
);
CRCHiTable : Array [0. . 255] of Byte =(
$00,$С0,$С1,$01,$СЗ,$03,$02,$С2,$С6,$06,$07,$С7,$05, $С5, $С4,$04,$СС,$0С,$0D,$CD,$0F,$CF,$CE,$0E,$0A,$CA, $CB, $0B,$C9,$09,$08,$C8,$D8,$18,$19,SD9,$1B,$DB,SDA, $1A, $1E,$DE,$DF, $1F,$DD,$1D,SIC,$DC,$14,$D4,$D5,$15, $D7,$17,$16,$D6f$D2,$12,$13,$D3,$ll,$Dl,$D0,$10,$F0, $3O,$31,$F1, $33,$F3, $F2,$32, $36,$F6,$F7,$37,$F5,$35, $34,$F4,$3C, $FC, $FD, $3D, $FF,$3F, $3E,$FE,$FA,$3A,$3B, $FB, $39,$F9,$F8,$38,$28,$E8,$E9,$2 9,$EB,$2B,$2A,$EA, $EE,$2E,$2F,$EF,$2D,$ED,$EC,$2C,$E4,$24,$25,$E5,$27, $E7,$E6,$26, $22,$E2,$E3,$23,$E1,$21,$20,$E0,$A0,$60, $61,5A1, $63, $A3, $A2, $62, $66, $A6, $A7,$67, $A5, $65, $64, $A4, $6C, SAC, $AD, $6D, $AF, $6F, $6E, SAE, $M,$6A, $6B, $AB, $69, $A9, $A8, $68, $78, $B8, $B9, $79, $BB, $7B, $7A, $BA, $BE, $7E,$7F,$BF,$7D,$BD,$BC,$7C, $B4,$74,$75,$E5,$77,$B7, $B6f$76f$72,$B2,$B3,$73,$Bl,$71,$70,$B0,$50,$90,$91, $51,$93, $53, $52, $92, $96, $56, $57,$97,$55,$95,$94,$54, $9C, $5C,$5D,$9D,$5F,$9F,$9E,$5E, $5A,$9A,$9B,$5B, $99, $59, $58,$98,$88,$48,$49,$89,$4B,$8B,$8A,$4A,$4E,$8E, $8F,$4F,$8D,$4D,$4C,$8C,$44,$84,$85,$45,$87,$47,$46, $86, $82,$42,$43,$83,$41,$81, $80, $40
);
Function CalculateQuickCRC(P : PChar; Len : Word) : Word; Var iByte : Word; CRCHi, CRCLo, Index : Byte; В : Byte; Begin
{$R-}
CRCHi := $FF;
CRCLo:= $FF;
For iByte:= 0 to Len-1 do begin
Index:= CRCLo xor Byte(Pointer(Longlnt(P)+iByte)^);
CRCLo:= CRCHi xor CRCLoTable[Index];
CRCHi:= CRCHiTable[Index];
End;
Result:= (CRCHi shl 8) or CRCLo;
{$R+}
End;
Еще один способ вычисления CRC16 заключается в создании единой таблицы для старшего и младшего байта. В этом случае расчет CRC сводится к выполнению одного цикла (листинг 15.7). Такой алгоритм применяется, например, в протоколе XModem.
j Листинг 15.7. Вычисление :таблицы CRC
контрольной суммы CRC16 с помощью
Const
CrcTable $0000,
array[0,.255; $1021, $2042,
of Word =( $3063, $4084,
$50a5,
$60c6,
$70e7,
$8108,
$9129,
$а14а,
$Ы6Ь,
$cl8c,
$dlad.
$elce.
$flef.
$1231,
$0210,
$3273,
$2252,
$52b5.
$4294,
$72f7.
$62d6.
$9339,
$8318,
$b37b.
$a35a,
$d3bd,
$c39c.
$£3ff.
$e3de,
$2462,
$3443,
$0420,
$1401,
$64e6,
$74c7,
$44a4,
$5485,
$а56а.
$Ь54Ь,
$8528,
$9509,
$e5ee,
$f5cf.
$c5ac.
$d58d.
$3653,
$2672,
$1611,
$0630,
$76d7,
$66f6.
$5695,
$46b4.
$Ъ75Ъ,
$а77а.
$9719,
$8738,
$f7df,
$e7fe,
$d79d,
$c7bc.
$48с4,
$58е5,
$6886,
$78а7.
S0840,
$1861,
$2802,
$3823,
$с9сс.
$d9ed.
$e98e,
$f9af.
$8948,
$9969,
$a90a.
$b92b.
$5а£5.
$4ad4.
S7ab7,
$6а96.
$la71.
$0a50.
$3a33.
$2al2,
$dbfd,
$cbdc.
$fbbf.
$eb9e.
$9b79,
$8b58.
$bb3b,
$abla,
$6са6.
$7с87.
$4ce4.
$5сс5.
$2c22.
$3c03.
$0c60,
$lc41,
$edae,
$fd8f.
$cdec.
$ddcd.
$ad2a.
$bd0b,
$8d68,
$9d49.
$7е97,
$6eb6.
$5ed5,
$4ef4,
S3el3,
$2e32.
$le51,
S0e70,
$ff9f.
$efbe.
$dfdd.
$cffc.
$bflb.
$af3a,
$9f59.
$8f78.
$9188,
$81а9,
$blca.
$aleb.
$dl0c,
$cl2d,
$fl4e.
$el6f.
$1080,
SOOal,
$30c2.
$20e3.
$5004,
$402S,
$7046,
$6067,
$83Ь9,
$9398,
$a3fb,
$b3da.
$c33d.
$d31c.
$e37f,
$f35e,
$02Ь1,
$1290,
$22f3.
$32d2,
§4235,
§5214,
$6277,
$7256,
$Ь5еа,
$a5cb.
$95a8,
$8589,
$f56e,
$e54f.
$d52c,
$c50d,
$34е2, $a7db, $26d3, $d94c, $5844, $cb7d, $4a75, $fd2e, $7c26, $eflf, $6el7,
$24c3, $b7fa, $36f2, $c96d, $4865, $db5c, $5a54, $edOf, $6c07, $ff3e, $7e36.
$14a0, $8799, $0691, $f90e, $7806, $eb3f, $6a37, $dd6c, $5c64, $cf5d, $4e55.
$0481, $97b8, $16b0, $e92f, $6827, $fble, $7al6, $cd4d, $4c45, $df7c, $5e74.
$7466, $e75f, $6657, $99c8, $18c0, $8bf9, $0afl, $bdaa, $3ca2, $af9b, $2e93,
$6447, $f77e, $7676, $89e9, $08el, $9bd8, $lad0, $ad8b, $2c83, $bfba, $3eb2,
$5424, $c71d, $4615, $b98a, $3882, $abbb, $2ab3, $9de8, $lce0, $8fd9, $0edl,
$4405, $d73c, $5634, $a9ab, $28a3, $bb9a, $3a92, $8dc9, $0ccl, $9ff8, $lef0
Function CalculateQuickCRCl(P : PChar; Len : Word) : Word;
Var iByte : Word;
Begin
{$R-}
Result: = 0;
For iByte:= 0 to Len-1 do begin
Result:= CrcTable[Result shr 8] xor (Result shl 8) xor Byte(Pointer(Longlnt(P)+iByte)八); End;
{$R+}
End;
Вычисление CRC32
Для увеличения достоверности данных можно увеличить число контрольных битов. В таких случаях применяют 4-байтовую контрольную сумму, называемую CRC32.
Алгоритм вычисления CRC32 практически не отличается от алгоритма вычисления CRC16, за исключением того, что все операции производятся над словами (т. е. по 2 байта). Пример реализации вычисления CRC32 показан в листинге 15.8.
Листинг 15.8* Вычисление контрольной суммы CRC32
Const
Сгс32ТаЫе : Array [0. .255] of Cardinal = ( $00000000, $77073096, $ее0е612с, $990951ba.
$076dc419, $0edb8832, $09b64c2b, $ldb71064, $ladad47d, $136c9856, $14015c4f, $3b6e20c8, $3c03e4dl, $35b5a8fa, $32d86ce3, $26d930ac, $21b4f4b5, $2802b89e, $2f6f7c87, $76dc4190, $71bl8589, $7807c9a2, $7f6a0dbb, $6b6b51f4, $6c0695ed, $65b0d9c6, $62ddlddf, $4db26158, $4adfa541, $4369e96a, $44042d73, $5005713c, $5768b525, $5edef90e, $59b33dl7, $edb88320, $ead54739, $е3630Ы2, $e40ecf0b, $f00f9344, $f762575d, $fed41b76, $f9b9df6f.
$706af48f, $79dcb8a4, $7ebl7cbd, $6ab020£2, $6ddde4eb, $646ba8c0, $63066cd9, $4c69105e, $4b04d447, $42b2986c, $45df5c75, $51de003a, $56b3c423, $5f058808, $58684cll, $01db7106, $06b6b51f, $0f00f934, $086d3d2d, $lc6c6162, $lb01a57b, $12b7e950, $15da2d49, $3ab551ce, $3dd895d7, $346ed9fc, $33031de5, $270241aa, $206f85b3, $29d9c998, $2eb40d81, $9abfb3b6, $9dd277af, $94643b84, $9309ff9d, $8708a3d2, $806567cb, $89d32be0, $8ebeeff9,
$e963a535, $e0d5e91e, $e7b82do7, $f3b97148, $£4d4b551, $fd62f97a, $fa0f3d63, $d56041e4, $d20d85fd, $dbbbc9d6, $dcd60dcf, $c8d75180, $cfba9599, $c60cd9b2, $cl611dabf $98d220bc, $9fbfe4a5, $9609a88e, $91646c97, $856530d8, $8208f4cl, $8bbeb8ea, $8cd37cf3, $a3bc0074, $a4dlc46d, $ad678846, $aa0a4c5f, $ЪеОЫО1О, $b966d409, $b0d09822, $b7bd5c3b, $03b6e20c, $04db2615, $0d6d6a3e, $0a00ae27, $le01f268, $196c3671, $10da7a5a, $17b7be43.
$9e6495a3, $97d2d988, $90bfld91, $84be41de, $83d385c7, $8a65c9ec, $8d080d£5, $a2677172, $a50ab56b, $acbcf940, $abdl3d59, $bfd06116, $b8bda50f, $Ы0Ъе924, $b6662d3d, $efd5102a, $e8b8d433, $el0e9818, $e6635c01, $f262004e, $f50fc457, $fcb9887c, $fbd44c65, $d4bb30e2, $d3d6f4fb, $da60b8d0, $dd0d7cc9, $c90c2086, $ce61e49f, $c7d7a8b4, $c0ba6cad, $74bld29a, $73dcl683, $7a6a5aa8, $7d079ebl, $6906c2fe, $6e6b06e7, $67dd4acc, $60b08ed5.
$d6d6a3e8, $dlbb67fl, $d80d2bda, $df60efc3, $сЬ61Ь38с, $ccOc7795, $c5ba3bbe, $c2d7ffa7, $9b64c2bO, $9c0906a9, $95bf 4a82, $92d28e9b, $86d3c12d4, $81bel6cd, $88085ae6, $8f659eff, $a00ae278, $a7672661, $aedl6a4a, $a9bcae53, $bdbdf21c, $bad03605, $Ь3667а2е, $b40bbe37,
);
$aldl937e, $a6bc5767, $af0alb4c, $a867d£55, $bc66831a, $bb0b4703, $b2bd0b28, $b5d0cf31, $ec63f226, $eb0e363f, $e2b87al4r $e5d5be0d, $fld4e242, $f6b9265b, $ff0f6a70, $£862ae69, $d70dd2ее, $d06016f7, $d9d65adc, $debb9ec5, $cabac28a, $cdd70693, $c4614ab8, $c30c8eal,
$38d8c2c4, $3fb506dd, $36034af6, $316e8eef, $256fd2a0, $220216b9, $2bb45a92, $2cd99e8b, $756aa39c, $72076785, $7bbl2bae, $7cdcefb7f $68ddb3f8, $6fb077el, $66063bca, $616bffd3, $4e048354, $4969474d, $40df0b66, $47b2cf7f, $53b39330, $54de5729, $5d681bo2, $5a05dflb.
$4fdff252, $48b2364b, $41047a60, $4669be79, $5268e236, $5505262f, $5cb36a04, $5bdeaeld, $026d930a, $05005713, $0cb61b38, $0bdbdf21, $lfda836e, $18b74777, $11010b5c, $166ccf45, $3903b3c2, $3e6e77db, $37d83bf0, S30b5ffe9, $24b4a3a6, $23d967bf, $2a6f2b94, $2d02ef8d
Function CalculateCRC32(P : PChar; Len : Word) : Cardinal;
Var iByte : Word; В : Byte;
Begin
{$R-}
Result := $FFFFFFFF;
For iByte:= 0 to Len-1 do begin
B:= Byte(Pointer(Longlnt(P)+iByte)^);
Result:= Crc32Table[Byte(Result xor Cardinal(B))]
xor ( (Result shr 8) and $00FFFFFF);
End;
Result := Result xor $FFFFFFFF;
{$R+}
End;
「лава 16
Интерфейс RS-485
Теперь у нас есть все функции для работы с интерфейсом RS-232. Небольшие изменения кода позволят нам использовать интерфейс RS-485, который является основой многих промышленных стандартов. Хотя RS-485 является аппаратной "разновидностью1* RS-232, он требует дополнительных программных решений и, по этой причине, рассматривается в отдельной главе.
Стандарт RS-485
Несмотря на рас п ростра не н i < ость, протокол RS-232 имеет несколько существенных недостатков:
口 необходимость трехпроводной линии;
口 длина соединяющих проводов не может превышать 10—15 метров;
口 к одному порту компьютера может быть подключен только один абонент.
Избавиться от этих недостатков помогает специальный интерфейс RS-485, для реализации которого достаточно двухпроводной линии, длина кабеля может достигать 1 км (для низких скоростей передачи до 3 км), а количество подключаемых к одному порту устройств ограничено 32 абонентами.
Обычная система, основанная на стандарте RS-485, содержит несколько приемников, несколько форм и ро вателе й и согласующих резисторов. Физически такая сеть представляет собой приемопередатчики, соединенные при помощи витой пары — двух скрученных проводов. Сигнал передается с помощью разности потенциалов между двумя проводами: по одному проводу идет оригинальный сигнал, а по второму —— его инверсная копия (этот принцип называется дифференциальной (балансной) передачей данных). Другими словами, если на одном проводе I, то на другом 0, н наоборот. Таким образом, между двумя проводами витой пары всегда есть разность потенциалов: при 1 она положительна, при 0 ——отрицательна. Такой способ передачи обеспечивает высокую помехоустойчивость, т. к. помеха действует
практически одинаково на оба провода, оставляя разницу потенциалов неизменной.
Аппаратная часть этой системы представляет собой микросхемы приемопередатчиков с дифференциальными входами/выходами (к линии) и цифровыми портами (к портам UART-контроля ера).
Интерфейс RS-485 представляет собой пол уду пл е кс ны й интерфейс. Прием и передача ведутся по одной паре проводов с разделением по времени. Во время приема отключается передатчик, а во время передачи ——приемник. Для переключения приема и передачи используется один из управляющих сигналов порта. Этот сигнал подается на вход “разрешение приемника**, а его инвертированное значение — на вход "разрешение передатчика*'.
Все устройства подключаются к витой паре одинаково: прямые выходы (называемые А) к одному проводу, а инверсные (называемые В) — к другому (рис. 16.1).
Витая пара
Рис. 16.1. Схема подключения устройств в RS-485
Количество подключаемых устройств ограничивается входным сопротивлением со стороны линии. Обычно эта величина составляет 12 кОм и, с учетом согласующих резисторов, ограничивает сеть 32 абонентами.
Более подробное обсуждение физической реализации сети выходит за рамки этой книги. Объяснения принципов построения сетей можно найти, например, в [2] и [4]. В |9| можно также найти хорошие рекомендации по правильной организации сетей.
Как работает COM-порт IBM PC
Прежде чем приступить непосредственно к программированию протокола, необходимо отметить некоторую специфику реализации п осл ед о вате л ьно го интерфейса в IBM PC.
Послеловательный интерфейс в IBM PC сделан буферным. При передаче символ закладывается в буфер, а затем уже оттуда передается в линию. Эта
тонкость никак не влияет на реализацию RS-232, однако оказывается существенной при реализации RS-485, т. к. абонентам сети необходимо переключать линию после получения идентификатора конца посылки. В IBM PC сделать это средствами, предоставляемыми микросхемой UART, невозможно.
Для демонстрации этого не вполне очевидного момента можно использовать простую программу, написанную на языке Pascal (DOS). Переключателем линии выберем вывод DTR, который будем сбрасывать в 0 при передаче данных от компьютера (листинг 16.1),
^Листинг 164 Пробная программа для тестирования сигналов порта
Uses Crt, RS232int;
Var DELAY1, DELAY2, DELAY3 : Word;
(* = Процедура передачи байта из модуля RS232工nt == Procedure OutByteToRS232(b:Byte); assembler;
Asm
@Wait_Line:
mov dx,3FDh{ регистр состояния линии )
in al,dx
test al,20h{ стык готов к передаче? }
jnz @Output{ да }
nop
loop @Wait_Line { нет, ждем } ret
0Output:
mov al, В
mov dx,3F8h { регистр данных } out dx,al{ вывести символ }
nop
End;
*)
Procedure SendOn;
Begin
Port[$3FC]:= 0; {включение передачи: DTR=0} End;
Procedure SendOff;
Begin
Fort[$3FC] := 1; {выключение передачи: DTR=1)
End;
Begin
DELAY1:= 10; DELAY2:= 10; DELAY3:= 10;
InitRS232(1, nil, 9600, 3); {инициализируем порт} SendOff;
While (True) do begin SendOn;
Delay (DELAY1);
0utByteToRS232 ($55);
0utByteToRS232 ($55);
0utByteToRS232 ($55);
Out ByteToRS232 ($55);
Delay (DELAY2);
SendOff;
Delay (DELAY3);
If (KeyPressed) then begin Case ReadKey of
{Клавиша ESC — выход}
#27: Break;
{изменение величины DELAY1} Ч1 : DELAY1: = DELAY1 + 10; 2: DELAY1 : = DELAY1 - 10;
{изменение величины DELAY2} '3': DELAY2:= DELAY1 + 10; ":DELAY2:= DELAY1 - 10; {изменение величины DELAY3} ‘51: DELAY1:= DELAY1 + 10; •6*: DELAY1:= DELAY1 - 10; End;
WriteLn (1 DELAY1= I DELAY1); WriteLn (' DELAY2= \ DELAY2);
WriteLn (' DELAY3=', DELAY3);
End;
End;
While KeyPressed do ReadKey;
CloseRS232;
End.
Подключив двухлучевой осциллограф к выводам передатчика и DTR, мы увидим картинку, показанную на рис. 16.2.
Пауза delays задает интервал между посылками, пауза de lay 1 — интервал между переключением линии и началом посылки.
Самая интересная величина ——пауза delay2, задающая интервал между окончанием посылки и переключением линии. Установим delay2 = о и замети м, что, несмотря на ожидание флага ''передатчик готов*' в процедуре OutByteToRS232 (мы специально привели ее полный код из модуля RS232int), линия DTR переключается примерно в начале отправки последнего байта.
Строго говоря, флаг "передатчмк готов*' вовсе не означает "6а曲т передан*'-
Рис. 16.2. Зависимость переключения линий от величин пауз DELAY тестовой программы для RS-485
Подведем итог эксперимента. Для правильной реализации протокола RS-485 необходимо задать величину delay2 таким образом, чтобы переключение
линии произошло заведомо после передачи последнего байта. Вычислить эту величину можно только приблизительно. Обычно это делается так, как показано в листинге 16.2.
Листинг 16.2. Вычисление паузы отправки полного пакета данных
{вычислим число бит, необходимых для передачи одного символа:}
число бит в байте + 1 старт-бит +- 2. стоп-бит
BitsPerChar :二 DCB.ByteSize + 2;
{если есть контроль четности, то еще один бит } if (Parity о 0 ) then inc(BitsPerChar);
{если используется 2 стоп-бита, то плюс еше один бит } if (StopBits = 2) then inc(BitsPerChar);
{Baud задается в бит/сек — вычисляем кол-во необходимых} {микросекунд на один бит}
MicroSecsPerBit := 10000000 div Baud;
{Микросекунд на буфер. 18 получено из 16 символов буфера} {ТХ FIFO, 1 символ в ТХ Output и 1 один символ, если он } {уже начал передаваться} MicroSecs := MicroSecsPerBit * BitsPerChar * 18;
{Ограничение снизу}
if (MicroSecs < 10000) then MicroSecs := MicroSecs + MicroSecs;
{Микросекунды переводим в миллисекунды} MilliSecs := Microsecs div 10000;
{Округляем вверх}
if( (Microsecs mod 10000) <> 0 ) then ire(MilliSecs);
{Пауза на полученное число миллисекунд} Sleep (MilliSecs);
Реализация RS-485
Теперь мы готовы к правильной реализации протокола RS-485. На основе нашего компонента TComBin мы создадим компонент тсош485, реализующий двухпроводной интерфейс.
Хотя мьг говорили, что для реализации различных протоколов нам не потребуется вносить изменения в код транспортного компонента, мы добавим в него пару свойств. Они никак не повлияют на наши предыдущие программы, но позволят включать ручное управление линиями RTS и DTR. Код, который нужно добавить в компонент TComPort, приведен в листинге 16.3.
Листинг 16.3. Свойства TComPort для управления линиями RTS и DTR
】,.工…:加《■.;」?$$:j::**1...,.一.. &工: .心.•.;%潟.....*,...;.;. “0.;….…:.… …士:公….…..4 工……,国 M...iI-.露露/;.:江 ;::
unit ComPort;
interface
uses
Windows, SysUtils, Classes, Forms;
Type
TCoinProp = class (TPersistent)
Private
FEnableRTS: Boolean;{разрешить управлять RTS}
FEnableDTR: Boolean;{разрешить управлять DTR}
Public
{разрешить управлять RTS}
Property EnableRTS : Boolean read FEnableRTS write FEnableRTS;
{разрешить управлять DTR}
Property EnableDTR : Boolean read FEnableDTR write FEnableDTR;
End;
function TComProp.GetDCB: TDCB; begin
{ Чистка структуры }
ZeroMemory(©Result, SizeOf(TDCB));
{ Пеле DCBLength должно содержать размер структуры } Result.DCBLength:= SizeOf(TDCB);
{ Скорость обмена (бод) } Result.BaudRate := WindowsBaudRates[FBaudRate];
{ Windows не поддерживает небинарный режим работы }
{ последовательных портов }
Result. Flags := dcb_Binary;
{ Число бит в байте }
Result.ByteSize := 5 + Ord(FByteSize);
{ Контроль четности } Result. Parity := Ord(FParity);
{ Число стоп-бит }
Result.StopBits := Ord(FStopbits);
If FEnableRTS then {разрешить управлять RTS}
Result. Flags: = Result. Flags or dcb_DtrControlEnable;
If FEnableDTR then {разрешить управлять DTR}
Result.Flags:= Result.Flags or dcb_RtsControlEnable; end;
End.
Код компонента тсот485 почти ничем не отличается от кода компонента TComBin, за исключением управления линией DTR, как показано в листинге 16.4.
Листинг 16.4. Управление линией DTR в протоколе RS-485
{Передача числа согласно протоколу}
procedure TCom485.SendValue(const AValue : Single);
Var CS : Byte;
begin
If Assigned (FComPort) then
If FComPort. Connected then begin
{Включаем DTR 一 передача}
FCanPort.ToggleDTR (True);
{Послать символ начала посылки}
FCcanPort.WriteBuffer(GFStartByte, SizeOf(FStartByte));
{Послать число}
FComPort.WriteBuffer(@AValue, SizeOf(AValue ));
{Послать символ завершения посылки}
FComPort.WriteBuffer(@FEndByte , SizeOf(FEndByte )); {контрольная сумма, если включено} If FUseCS then begin CS:= GetCSSingle(AValue); FComPort.WriteBuffer(@CS, SizeOf(CS));
End;
{Пауза для полной отправки посылки}
Sleep(FComPort.ComProp.GetWaitTxDelay);
{Выключаем DTR - конец передачи}
FComPort.ToggleDTR(True);
End;
end;
Глава 17
Работа Plug and Play
В гл. 5 мы описали, как работает Plug and Play для послеловательных портов. В этой главе мы проведем несколько экспериментов и создадим программу, эмулирующую с помощью порта СОМ1 некоторое устройство, подключенное к COM2.
Эмулятор устройства
Наша задача — заставить Windows "поверить”,что к порту COM2 подключено некое устройство, поддерживающее стандарт РпР. Так как мы договорились, что порты нашего компьютера соединены нуль-модемным кабелем, то для решения этой задачи нужно написать программу, отвечающую на запросы, поступающие к порту COM2 через порт СОМ1 (рис. 17.1).
Рис. 17.1. Структурная схема эмулятора Plug and Play
Для реализации такой системы мы воспользуемся компонентом TComPort. На главную форму программы мы вынесем составляющие РпР-иден- тификатора и окно сообщений, в которое будем выводить производимые действия. Поскольку РпР работает на фиксированной скорости обмена, то кнопка Настройка порта нам не потребуется. Также мы удалим кнопку
Опрос порта. В результате у нас должна получиться форма, показанная на рис. 17.2.
Рис. 17.2. Вид программы эмулятора РпР-устройства
Все поля PnP-идентификатора нам уже известны из л?. 5. Единственная тонкость — создание бинарного значения поля PnP Rev из его обычного значения. Для этого нам нужно провести операции, обратные описанным: умножить номер версии на 100 и выделить первые 6 бит и следующие 6 бит. Как это сделать, показано в листинге 17.1.
Лисшнг 17.1. Формирование ПОЛЯ PnP Rev
Function TForml.GetPnPRevison(RevN : String) : String;
Var iRev : Integer; iRevH, iRevL : Byte;
Begin
{ RevN = строковое значение, например, 1.О }
{ Получаем iRev — цифровое значение } iRev:= Trunc(StrToFloatDef(RevN, 0)*100);
{ Вьщеляем старшие 6 бит и младшие 6 бит} iRevH:= (iRev shr 6) and $3F;
iRevL: = iRev and $3F;
{ Создаем строку из двух символов} Result:= Char(iRevH)+Char(iRevL);
End;
При формировании PnP-идентификатора не следует забывать, что контрольная сумма считается, включая значения полей Begin id и End id, но не включая значения самой контрольной суммы. Именно поэтому мы сначала вычислим "внутренности" PnP-идентификатора, потом добавим скобки (мы выбрали 7-битовый набор символов), затем подсчитаем контрольную сумму, и только после этого сформируем полный РпР-идентификатор.
Собственно сам алгоритм, реализующий ответы на запросы РпР-протокола, достаточно прост и полностью совпадает с алгоритмом протокола, описан^ ным в гл. 5. Он запускается кнопкой Запустить! и работает до тех пор, пока открыт выбранный порт. Полный код получившейся программы показан в листинге 17.2.
:Листинг 17-2. Код программы РпР-змулятора
unit Unitl;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Buttons, Spin, Mask, COmPort, ComCtrIs;
type
TForml = class(TForm) Pane11: TPanel; Pane12: TPanel; btnClose: TBitBtn; ParamPane1: TPane1;
rgPortIndex: TRadioGroup;
GroupBoxl: TGroupBox; btnOpenPort: TSpeedButton; btnClosePort: TSpeedButton; StatusBar: TStatusBar;
IbTrace: TListBox;
FPort: TComPort; Pane14: TPanel; bRead: TSpeedButton; btnClearLog: TSpeedButton; Panel3: TPanel;
GroupBox2: TGroupBox; PnP_Rev: TLabeledEdit; EISA_ID: TLabeledEdit; Producr_ID: TLabe LedEdir; GroupBox3: TGroupBox;
Serial_Number: TLabeledEdit; Driver_ID: TLabeledEdit;
User_Name: TLabeledEdit; Class_Name: TComboBox; Label1: TLabel;
procedure procedure procedure procedure procedure procedure procedure procedure
private
Function <
FormCreare(Sender: TObject);
btnCloseClick(Sender: TObject);
bReadClick(Sender: TObject);
btnOpenPortClick(Sender: TObject);
btnClosePortClick(Sender: TObject);
rgPortIndexClick(Sender: TObject);
btnClearLogClick(Sender: TObject);
FormCloseQuery(Sender: TObject; var CanClose: Boolean);
Function
GetPnPRevison(RevN : String) : String; GetPnPCS(SID : String) : Byte;
public function
procedure end;
MakePnP_ID : String;
SetEnabledControls;
var
Fo rml: T Fo rml;
implementat i on
{$R *.DFM}
{ Создание компонента доступа к портам при создании форьлы } procedure TForml. FormCreate (Sender: TObj ect);
begin
Decimal Separator: = ' . 1;
SetEnabledControls;
end;
{Изменение номера порта}
procedure TForml.rgPortlndexClick(Sender: TObject);
begin
FPort. ComNuinber: = rgPort Index. Itemlndex+l;
end;
{Кнопка открытия порта}
procedure TForml.btnOpenPortClick(Sender: TObject);
begin
FPort .Close;
FPort. ComNuinber: = rgPortlndex. Itemlndex+l;
FPort .Open;
If FPort.Connected then
StatusBar.Panels[0].Text:= 'Порт успешно открыт'
Else
StatusBar.Panels[0].Text:= 'Ошибка открытия порта';
SetEnabledControls;
If FPort.Connected then begin
FPort.ToggleRTS(False);
FPort.ToggleDTR(False);
End;
end;
{Кнопка закрытия порта}
procedure TForml.btnClosePortClick(Sender: TObject);
begin
FPort .Close;
StatusBar.Panels[0].Text:='';
SetEnabledControls; bRead.Down:= False; end;
{Установка активности элементов формы} procedure TForml.SetEnabledControls; Var Active : Boolean;
begin
Active:= FPort.Connected; btnClosePort.Enabled:= Active; rgPortIndex.Enabled := not Active; btnOpenPort.Enabled := not Active; bRead.Enabled:= Active;
end;
{Кнопка закрытия программы}
procedure TForml.btnCloseClick(Sender: TObject); begin
Close; end;
{ Получение контрольной суммы строки РпР-идентификатора } Function TForml.GetPnPCS(SID : String) : Byte;
Var i : Integer;
Begin
Result:= 0;
SID:= '(' + SID + ')'; {суммируем}
For i:= 1 to Length(SID) do begin {$R-}
Result:= Result + Byte(SID[i]);
{$R+}
End;
End;
{ Формирование поля PnP Rev }
Function TForml.GetPnPRevison(RevN : String) : String;
Var iRev : Integer; iRevH, iRevL : Byte;
Begin
{ ReW = строковое значение, например, 1.О }
{ Получаем iRev - цифровое значение } iRev:= Trunc(StrToFloatDef(RevN, 0)*100);
{ Выделяем старшие 6 бит и младшие 6 бит}
iRevH:= (iRev shr 6) and $3F;
iRevL:= iRev and $3F;
{ Создаем строку из двух символов} Result := Char (iRevH) +Char (iRevL);
End;
{ Формирование PnP-идентификатора}
Function TForml. MakePnP_ID : String;
Begin
{Внутреннее содержание PnP ID}
Result:=
Format(*%s%3s%4s\%s\%s\%s\%s*,[ GetPnPRevison(PnP_Rev.Text), EISA_ID.Text, Product_ID.Text, Serial_Numbe r.Text, Class_Name•Text, Driver_ID.Text, User_Name.Text
]);
{ Должно получиться что-то вроде строки: }
{ "#$01#$24,PVA0000\12345678\MULTIFUNCTION\BEER001, BEER002\Bottle Opener" }
{ Добавить BeginlD, EndID и контрольную сумму }
Result:= FormatC1(%s%s)*,[Result, IntToHex(GetPnPCS(Result),2)]);
End;
{Запустить! }
procedure TForml.bReadClick (Sender: TObject);
begin
StatusBar.Panels[0] .Text: = MakePnP_ID;
IbTrace. Items. Add (' Пуск...*);
IbTrace. Items. Add('Настройка порта: 1200/7/NONE/11);
FPort.ComProp.BaudRate:= brl200;
FPort.ComProp.ByteSize:= bs7;
FPort.ComProp.Parity := ptNONE;
FPort.ComProp•Stopbits:= sblBITS;
While FPort.Connected do begin
IbTrace.Items.Add(тЖдем появления DSR...*);
While (FPort.GetModemState and MS_DSR_ON) = 0 do Application.ProcessMessages;
IbTrace.Items.Add('Устанавливаем в ответ DTR');
FPort.ToggleDTR(True);
IbTrace.Items.Add('笊дем установки CTS...*);
While (FPort.GetModemState and MS_CTS_ON) = 0 do Application.ProcessMes sages;
IbTrace.Items.Add('Передаем идентификатор *);
FPort.WriteString(MakePnP_工D);
End;
IbTrace. Items. Add (* END*);
end;
{Кнопка очистки лог-файла}
procedure TForml.btnClearLogClick(Sender: TObject);
begin
IbTrace.Items.Clear;
end;
{Закрытие порта перед закрытием формы, }
{позволяющее завершиться циклу РпР-эглуляции}
procedure TForml.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
FPort.Close;
end;
end.
Мы надеемся, что такое полезное устройство, как Bottle Opener (открывалка бутылок) обязательно пригодится вам в вашей работе!
Итак, все готово. Запускаем программу. Открыв порт СОМ 1, нажимаем кнопку Запустить!. Теперь надо активизировать алгоритм обнаружения новых устройств. Сделать это можно разными путями, которые описаны в 已?. 5.
В Windows 98 возможны проблемы, и, может быть, придется сначала удалить СОМ-порты из системы, а затем найти их заново.
После нескольких секунд работы РпР-скапер обратится к порту COM2 и обнаружит, что появилось новое устройство (рис. 17.3)!
Рис. 17.3. Windows 2000 обнаружила новое устройство
После обнаружения нового устройства Windows предложит поискать и установить драйвер для него (рис. 17.4). Этим мы займемся в следующем разделе.
Рис. 17.4. Windows 2000 предлагает установить драйвер
Установка драйвера устройства
Как мы уже описывали в 勧.5, для установки драйвера устройства Windows ищет специальный файл с расширением inf. Этот файл содержит всю информацию о действиях, которые необходимо произвести для установки драйвера: какие файлы нужно перенести и зарегистрировать в системе, какие ветки реестра необходимо создать и т. д. Кроме того, этот же файл содержит информацию о действиях, которые нужно произвести при удалении устройства из системы, а также дополнительную информацию о производителе устройства.
"За кадром'* остался довольно интересный вопрос: какой именно INF-файл ищет Windows при установке нового устройства? Очевидно, что совершенно произвольный файл не подходит.
Для ответа на этот вопрос приведем листинг “правильного" INF-файла для нашего устройства (листинг 17.3).
I Листинг 17.3. INF-файл для нового устройства
;INF-файл для устройства Bottle Opener
[Version]
;Сигнатура ндля всех версий Windows" signature="$CHICAGO$"
;Класс устройства
Class=MODEM
;Ссылка на строку из раздела Strings Provide r=%Mfg%
;Производитель — для каждого производителя
;указывается ссылка на соответствующую
;секцию описания
[bdanufacturer]
%Mfg%=CompanyName
;Директория
[DestinationDirs]
DefaultDestDir=30,MyFolder
[CoinpanyNaine ]
;Ссылка на раздел MyDevice SECTION для устройств.
;подключе иных к последовательному порту с серийным ;номером PVA0000
%MyDeviceStr%=MyDevice_SECTlON, SERENUM\PVA000О
;Свойства устройства — изменения реестра, ;копируемые файлы
[MyDevice_SECTION ]
CopyFiles=COPY_SECTION
AddReg=MyDevice. AddReg DelReg=MyDevice. DelReg LogConf ig=MyDevice • Config
[MyDevice. AddReg]
;ничего не записываем в реестр при install
[MyDevice .DelReg]
;ничего не будем удалять при uninstall
[MyDevice. Config] ;ничего не конфигурируем
[COPY_SECTION]
;драйвер устройства — это notepad. ехе notepad.exe ;= win.ini ;=
;строки. Эта секция может меняться для разных языков [Strings ]
Mfg=',PVASoft,'
MyDeviceStr= иPVASoft Bottle Opener*'
Изменяя значения полей, легко выяснить, что контрольными полями при установке драйвера являются:
口 поле signature секции version должно содержать правильную версию Windows или строку ”$chicago$", говорящую, что драйвер подходит для всех версий Windows;
口 поле class секции version должно содержать правильный идентификатор класса, который может не совпадать с идентификатором, переданным устройством, но должен быть зарезервированным словом из табл. 5.3;
□ секция companyName (правильнее сказать ——та секция, на которую делается ссылка из Manufacturer для выбранного производителя устройства) должна содержать поле, в левой части которого указан тип подключения (в нашем случае это serenum ——устройство, подключаемое к последовательному порту), а в правой ——идентификатор устройства, совпадающий со строками “ID производителя'* и “ID продукта** (в нашем случае это pvaoooo). разделенные знаком ”\”.
Секция company Name может содержать описание нескольких устройств с разными серийными номерами. Для каждого серийного номера указывается своя секция установок (в нашем файле это секция MyDevice_SECT工on). Секция установок содержит ссылки на секции CopyFiies (копируемые файлы), AddReg (регистрация в реестре при установке устройства), DeiReg (удаляемые из реестра ветки при удалении устройства) и LogConfig (конфигурирование устройства).
Для описания нескольких устройств с разными серийными номерами можно использовать разделитель
%MyDeviceStr% = MyDevic6_SECT工ON, SERENUM\PVAO 001, PVA0000
Рис. 17.5. Установка драйвера устройства
Если файл сформирован правильно, Windows установит новое устройство. В качестве файла драйвера мы указали стандартный Блокнот (Notepad) и
файл win.ini. При установке Windows спросит о его местонахождени и. После установки новое устройство будет добавлено в систему (рис. 17.5).
Естественно, Windows будет сообщать об ошибках в драйвере, т. к. собственно драйвер мы и не установили, а Блокнот (Notepad) не знает, как отвечать на запросы Windows. Тем не менее, новое устройство будет успешно добавлено в систему (рис. 17.6).
旦 Commiter Management旦 Commiter ManagementAction View [j 的Action View [j 的宣國甯К max宣國甯К maxTreeTree旦 Computer Management [Local' ♦-遮 System Tools+ 劃 Event Viewer+ 斗 System InJorrnation+ 翅 Performance Logs -зпе旦 Computer Management [Local' ♦-遮 System Tools+ 劃 Event Viewer+ 斗 System InJorrnation+ 翅 Performance Logs -зпеShared Folders Device Manager Local Users and Grou(Shared Folders Device Manager Local Users and Grou(-•③ Storage.1 Disk Managemenl 駐 Disk Defragmenter 亠 Logical Drives+ ^ Removable S lor age1LI 2J-•③ Storage.1 Disk Managemenl 駐 Disk Defragmenter 亠 Logical Drives+ ^ Removable S lor age1LI 2JI-豊 FVA+ 3 Computer+ —J Disk drives+ 理 Displav adapters+ 3 DVD/CD-ROM drives+」Floppy disk controllers+ H Floppy disk drives十 7j IDE ATA/ATAPI controllers+ 姿 Keyboards+ ' j Mice and ether pcnnting devices+ ^1 M onitors-\y Mulbfunction adaptersI-豊 FVA+ 3 Computer+ —J Disk drives+ 理 Displav adapters+ 3 DVD/CD-ROM drives+」Floppy disk controllers+ H Floppy disk drives十 7j IDE ATA/ATAPI controllers+ 姿 Keyboards+ ' j Mice and ether pcnnting devices+ ^1 M onitors-\y Mulbfunction adapters邑AS 嚥 Battle Qppned邑AS 嚥 Battle Qppned+ 矍学 Network adapters+ * Ports (COM & LPTJ+ lE EnunH viiiftn япН пяте rnntinllftrs+ 矍学 Network adapters+ * Ports (COM & LPTJ+ lE EnunH viiiftn япН пяте rnntinllftrs
旦 Commiter Management
旦 Commiter Management
Action View [j 的
Action View [j 的
宣國甯К max
宣國甯К max
Tree
Tree
旦 Computer Management [Local' ♦
-遮 System Tools
+ 劃 Event Viewer
+ 斗 System InJorrnation
+ 翅 Performance Logs -зпе
旦 Computer Management [Local' ♦
-遮 System Tools
+ 劃 Event Viewer
+ 斗 System InJorrnation
+ 翅 Performance Logs -зпе
Shared Folders Device Manager Local Users and Grou(
Shared Folders Device Manager Local Users and Grou(
-•③ Storage
.1 Disk Managemenl 駐 Disk Defragmenter 亠 Logical Drives
+ ^ Removable S lor age
1LI 2J
-•③ Storage
.1 Disk Managemenl 駐 Disk Defragmenter 亠 Logical Drives
+ ^ Removable S lor age
1LI 2J
I-豊 FVA
+ 3 Computer
+ —J Disk drives
+ 理 Displav adapters
+ 3 DVD/CD-ROM drives
+」Floppy disk controllers
+ H Floppy disk drives
十 7j IDE ATA/ATAPI controllers
+ 姿 Keyboards
+ ' j Mice and ether pcnnting devices
+ ^1 M onitors
-\y Mulbfunction adapters
I-豊 FVA
+ 3 Computer
+ —J Disk drives
+ 理 Displav adapters
+ 3 DVD/CD-ROM drives
+」Floppy disk controllers
+ H Floppy disk drives
十 7j IDE ATA/ATAPI controllers
+ 姿 Keyboards
+ ' j Mice and ether pcnnting devices
+ ^1 M onitors
-\y Mulbfunction adapters
邑AS 嚥 Battle Qppned
邑AS 嚥 Battle Qppned
+ 矍学 Network adapters
+ * Ports (COM & LPTJ
+ lE EnunH viiiftn япН пяте rnntinllftrs
+ 矍学 Network adapters
+ * Ports (COM & LPTJ
+ lE EnunH viiiftn япН пяте rnntinllftrs
Рис. 17.6. Новое устройство установлено
Заметьте, что на системном диске создалась директория MyFolder, в которую скопированы файлы notepad.exe и win.ini. Эту директорию мы указали в секции DestinationDirs, а список файлов в секции COPY_SECTION.
Обнаружение изменений
Еще один вопрос, часто возникающий при программировании: как в другой программе обнаружить изменения аппаратной конфигурации?
Сделать это очень просто. При изменениях в аппаратной конфигурации Windows рассылает сообщение wm_dev 1 cechange, передающее структуру TWMDeviceChange. Эта структура описывается следующим образом:
type
TWMDeviceChange = record
Msg:Cardinal;
Event: U工NT;{ код события }
dwData: Pointer; { данные Result: Longlnt;
end;
Код события —— одна из констант dbt_xxx. Описания этих констант нет в Delphi (по крайней мере, в Delphi версий 3—7), поэтому нам придется позаимствовать их из файла dbt.h набора заголовков Visual Studio. Полный файл dbt.pas можно найти на прилагаемом компакт-диске, а нас, прежде всего, интересуют константы:
DBT_DEV 工 CEARR 工 VAL= $8000
DBT__DEVICEREMOVECOMPLETE = $8004
Первый код события означает обнаружение нового устройства, а второй код ——отключение устройства. Следует отметить, что сообщение о подключении устройства будет послано только в случае успешной установки устройства, т. е. в нашем случае оно послано не будет: мы не установили реальный драйвер устройства, и Windows законно считает, что устройство установлено с ошибкой. А вот подключение или отключение флеш-диска будет зареги стри ро ван о.
В Windows 2000 добавлена еще одна константа
DBT_DEVNODES_CHANGED = $0007
Это событие сообщает об изменении в списке узлов дерева устройств в Менеджере устройств (Device Manager) (рис. 17.6). В нашем случае был добавлен узел Multifunction Adapter, поэтому мы получим это сообщение, несмотря на то, что наше устройство подключено некорректно. Вообще говоря, изменение в списке самих устройств вызывает изменение узла дерева устройств, поэтому это сообщение мы будем получать достаточно часто.
Итак, для примера напишем небольшую программу, которая производит обработку сообщения wm dev工cechange и отображает результат на экране Собственно, весь код этой программы будет сводиться к обработчику сообщения (листинг 17.4).
Листинг 17.4. Обработка сорбщения wm_devicechange
:…;;Л ;.“*”::".<..,而…>.6…證….二…:Л … 4'……w 了…….:….U.…";…3………:::""…;£……品%:
unit Uni匸1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, DBT;
type
TForml = class(TForm)
IbDevice: TListBox;
private
{обработчик сообщения WM_DEVICECHANGE}
procedure WMDEVICECH2XNGE(var Msg : TWMDeviceChange); message WM—DEV 工 CECHANGE ;
public
end;
var
Forml: TForml;
implement at i on
{$R * .dfm}
{обработчик сообщения WM_DEVICECHANGE}
procedure TForml.WMDEVICECH7XNGE(var Msg: TWMDeviceChange);
var
Ipdb : PDevBroadcastHdr;
Ipdbv : PDevBroadcastVolume;
Ipdbpr: PDevBroadCastPort;
begin
{Заголовок сообщения}
Ipdb := PDevBroadcastHdr(Msg.dwData);
{Отображаем код события}
IbDevice. Items.Add('Обнаружено событие. Код: ' + IntToHex (Msg.Event, 4));
Case Msg. Event of
DBT_DEVICEARRIVAL: begin {Добавление}
IbDevice. Items. Add (' >Добавлено устройство. Код: ' 4-
IntToHex(Ipdb人.dbch_devicetype, 4));
{ Новое устройство — порт (последовательный или параллельный) }
If lpdbA.dbch_devicetype = DBT_DEVTYP_PORT then begin
Ipdbpr:= PDevBroadCastPort(Msg.dwData);
IbDevice.Items.Add('»Добавлен порт. Имя:'+Ipdbpr.dbcp_name);
End;
{ Новое устройство — логический диск }
If Ipdb^.dbch_devicetype = DBT_DEVTYP_VOLUME then begin
Ipdbv : = PDevBroadcastVolvime (Msg. dwData);
IbDevice.Items.Add('»Добавлен логический диск *);
End;
End;
DBT_DEVICEREMOVECOMPLETE: begin {Удаление}
IbDevice.Items.Add('>Удалено устройство.
Код:1+IntToHex(Ipdb人.dbch_devicetype, 4));
{ Удаленное устройство — порт }
{(последовательный или параллельный) }
If 1рбЬл.dbch_devicetype = DBT_DEVTYP_PORT then begin
Ipdbpr:= PDevBroadCastPort(Msg.dwData);
IbDevice.Items.Add(1»Удален порт. Имя:'+Ipdbpr.dbcp_name);
End;
{ Удаленное устройство -логический диск }
If Ipdb^.dbch_devicetype - DBT_DEVTYP_VOLUME then begin
Ipdbv := PDevBroadcastVolume(Msg.dwData);
IbDevice.Items.Add('»Удален логический диск');
End;
End;
End;
end;
end.
Для демонстрации мы будем обрабатывать два события: добавление или удаление порта (последовательного или параллельного) и добавление или уда-
ление логического диска. Работу первого сообщения можно проверить, установив опцию Выключено (Disable) для порта СОМ1 и затем, включив его снова (рис. 17.7).
4r Мониторинг РпР устройств HS ЕЭ
Обнаружен собьггие... Код :Ш Обнаружено событие. Код:8004 > Удалено устройство. Код: 0003 » Удален порт. Имя:С0М1 Обнаружено собьгие. Код: 0007 Обнаружено событие. Код:0007 Обнаружено событие. Кад:0007 Обнаружено событие. Кш:0007
Рис. 17.7. Обнаружение измененийв аппаратной конфигурации
ЧАСТЬ III
Справочник
Глава 18
Сервис BIOS INT14H/INT21H
Сервис BIOS INTI4Н
Порты персонального компьютера поддерживаются сервисом BIOS INT14H, который имеет следующие функции:
О 00 — инициализация порта;
01 — передача байта;
02 — прием байта;
口 03 —— получение состояния порта;
04 — расширенная инициализация;
П 05 — расширенное управление портом.
Две последние функции применяются только в System/2.
Для вызова конкретной функции ее номер задается в регистре ан (листинг 18.1). Номер порта задается в регистре dx и кодируется следующим образом: значение 0 задает порт СОМ1, значение 1 задает COM2 и т. д. Точнее говоря, ан задает номер канала в соответствии с базовым адресом портов в таблице по адресу 40:0.
В зависимости от функции входные параметры могут указываться в регистрах зн, al, сн, cl. При выходе все регистры, кроме регистра ах, сохраняются (это правило не распространяется на использование расширенных функций).
J^|«*^••e■^"•■^"•^^•«'•"•^«-••^'«■^^«'•••■?«"■;?'^^'л■^.■»^,!•"^■^^vл;■^^^^.■"•••^^•--■■■;••■•••..«.>..,«»».....«.я,.,.„у,...».л,^,,,.,..я.я.,ял,,..,,.,_,,„...,■.,■.,,,.,fM......ff.v. ,„..,,.
Листинг 18.1. Вызов INT14H
MOV АН,
номер_функции
MOV DX,
номер_порта ; (0 — COM1, 1 — COM2,
MOV AL,
параметр-1 ; если надо
MOV BH,
параметр-2; если надо
MOV CH,
параметр-3 ; если надо
MOV CL,
параметр-4; если надо
INT 14H
;выход — регистры АН и (или) AL
Ниже мы перечислим все функции сервиса INT14H. При описании будем использовать несколько сокращений. Если регистр должен иметь фиксированное значение, мы будем писать знак равенства ”=”,например ан = з. Биты регистра будем указывать в квадратных скобках, например дн[4:3] будет означать биты 4 и 3 регистра ан. Если значение какого-либо бита не существенно, мы будем обозначать его символом ”х”. Например, запись ихГ означает, что первый бит не важен, а второй должен равняться 1.
Функция 00: инициализация порта
Функция 00 инициализирует порт. Ее входными регистрами являются: □ АН = 0;
П dx — номер порта;
□ al — флаги инициализации порта:
биты [7:5] задают скорость обмена:
0 000 — 110 бит/с;
0 001 — 150 бит/с;
0 010 — 300 бит/с;
0 011 — 600 бит/с;
0 100 — 1200 бит/с;
0 101 — 2400 бит/с;
0 НО - 4800 бит/с;
0 111 — 9600 бит/с;
биты [4:3] задают код контроля четности:
0 хО — нет;
0 01 — нечет;
0 II — чет;
бит 2 задает число стоп-бит:
0 0 — 1 стоп-бит;
0 1 — 2 стоп-бита;
биты [1:0] задают длину слова:
0 00 — 5 бит;
0 01—6 бит;
0 10 — 7 бит;
0 11 — 8 бит.
На выходе функция возвращает статус линии в регистре ан, как описано в
разд. 18.1.4.
По умолчанию (при запуске DOS) порт СОМ1 инициализируется так:
2400 бит/с;
口 без проверки на четность;
1 стоп-бит;
8-битовые слова.
Команда mode используется для установки иных характеристик.
Функция 01: посылка символа в порт
Функция 01 передает один символ в указанный порт. Входными регистрами являются:
口 АН = 1;
□ dx — номер порта;
口 al — посылаемый символ.
На выходе регистр al будет сохранен. Если в регистре ан установлен бит 7, это свидетельствует об ошибке, и биты ан [6: о] хранят статус линии связи (см. разд. 18.1.4).
Функция 02: получение символа
Функция 02 принимает один символ с выбранного коммуникационного порта. Входными параметрами функции является содержимое двух регистров:
АН = 2;
dx — номер порта.
На выходе в регистре al содержится полученный символ. Если произошла ошибка, регистр ан будет содержать ненулевое значение.
Функция 03: получить статус порта
Функция 03 возвращает статус порта. Эту функцию следует вызывать, чтобы узнать готовность порта к приему или передаче. Входными параметрами функции 03 является содержимое двух регистров:
口 АН = 3;
dx — номер порта.
На выходе регистр ан содержит статус линии:
П бит 7 — тайм-аут;
口 бит 6 — регистр сдвига передатчика пуст (пауза передачи);
口 бит 5 ——промежуточный регистр передатчика пуст (готов принять символ для передачи);
口 бит 4 — обнаружен обрыв линии;
口 бит 3 — ошибка кадра (отсутствие стоп-бита);
П бит 2 ——ошибка паритета принятого символа;
бит 1 ——переполнение (потеря символа);
бит 0 ——регистр данных содержит принятый символ.
Регистр al содержит статус состояния модема:
口 бит 7 — состояние линии DCD;
П бит 6 — состояние линии RI;
П бит 5 ——состояние линии DSR;
口 бит 4 —— состояние линии CTS;
口 бит 3 ——изменение состояния DCD;
П бит 2 — изменение огибающей RI;
П бит 1 ——изменение состояния DSR;
П бит 0 ——изменение состояния CTS.
Функция 04:
расширенная инициализация (System/2)
Функция 04 осуществляет расширенную инициализацию порта и присутствует только в System/2. Входные параметры функции содержатся в следующих регистрах:
П АН = 4;
口 dx — номер порта (0—3) в соответствии с базовым адресом портов в таблице по адресу 40:0;
□ al — обработка сигнала Break:
0 — без обработки Break;
1 — с обработкой Break;
П вн — контроль паритета:
0 ——нет контроля;
1 — контроль по нечетности;
2 — контроль по четности;
3 — постоянный нечет;
4 — постоянный чет;
口 bl — число стоп-битов:
0 — 1 стоп-бит;
1 — 2 стоп-бита для 6——8-битового кода, 1,5 стоп-бита для 5-битового кода;
П сн — число битов в байте:
0 — 5-битовый код;
1 — 6-битовый код;
2 — 7-битовый код;
3 — 8-битовый код;
cl — скорость обмена:
0—110 бит/с;
1 — 150 бит/с;
2 — 300 бит/с;
3 — 600 бит/с;
4 — 1200 бит/с;
5 — 2400 бит/с;
6 — 4800 бит/с;
7 — 9600 бит/с;
8 — 19 200 бит/с.
На выходе регистр ан будет содержать состояние линии управления, а регистр al — состояние модема.
Функция 05:
расширенное управление портом (System/2)
Функция 05 обеспечивает расширенное управление портом и присутствует только в System/2. Функиия выполняет две подфункции в зависимости от значения регистра al.
Входные параметры первой подфункции:
АХ = 0500Н;
dx — номер порта (0—3) в соответствии с таблицей базовых адресов по адресу 40:0.
Выходом является состояние регистра управления модемом в регистре bl:
биты [7:5] — нули;
бит 4 — цикл (возможность диагностирования);
бит 3 — управление выходом OUT2;
бит 2 — управление выходом OUTI;
бит 1 — запрос на передачу;
口 бит 0 — терминал данных готов (DTR).
Входные регистры второй подфункции:
口 АХ = 0501Н;
dx —— номер порта (0—3) в соответствии с таблицей базовых адресов по адресу 40:0;
bl ——состояние регистра управления модемом (как в функции 0500Н).
На выходе в регистре ан содержится состояние линии управления, а в регистре al — состояние модема (как в функции 03, см. разд. 18.1.4).
18.2. Сервис BIOS INT21Н
Функции 03Н, 04Н прерывания INT 21Н позволяют работать с коммуникационным портом СОМ1 (который также называется стандартным вспомогательным устройством, AUX (AUXiliary)). Для работы с COM2 необходимо поменять местами базовые адреса портов в области 40:0 (см. листинг 6.3).
Для DOS коммуникационный порт является последовательным файлом, доступным для чтения и записи. Для работы с файлами предусмотрены функции 3FH и 40Н прерывания INT21H. Эти функции работают с файла* ми через специальный идентификатор файла ——,,описатель,* (прототип дескриптора файла в Windows). Для работы со стандартными устройствами зарезервированы несколько первых номеров описателей:
П 0 — стандартное устройство ввода (обычно клавиатура);
П 1 — стандартное устройство вывода (обычно экран);
2 — стандартное устройство ошибок (всегда CON-экран);
3 — стандартное устройство AUX (nepBbiii порт COMI);
4 — стандартный принтер (LPTI).
Таким образом, для коммуникационного порта зарезервирован описатель 3
Функция 03H: вспомогательный ввод
Эта функция считывает (ожидает) символ со стандартного вспомогательного устройства СОМ1 и возвращает этот символ в al. Ввод не буферизируется и должен опрашиваться (не управляется прерываниями).
На входе задается только номер функции в регистре ан (ан = з), на выходе bal записан символ, полученный от AUX.
Функция 04Н: вспомогательный вывод
Функция 04Н посылает символ в dl на стандартное вспомогательное устройство. Входными регистрами являются:
口 АН = 4;
□ dl — символ, выводимый на AUX/COM1.
Выходных регистров нет.
Функция 3FH:
чтение данных через описатель
При вызове этой функции количество байтов, равное сх, считывается из файла или устройства с описателем, указанным в вх. Данные читаются с текущей позиции указателя чтения/записи файла и помещаются в буфер вызывающей программы, адресуемый через ds:dx. Итак, входными регистрами являются:
Л АН = 3FH;
О вх — описатель файла;
口 ds:dx ——адрес буфера для чтения данных;
О сх — число считываемых байтов (обычно это размер буфера ds: dx).
После вызова необходимо сравнивать возвращаемое значение ах (число прочитанных байтов) с сх (запрошенное число байт):
если ах = сх и сг сброшен — чтение без ошибок;
口 если ах = о, достигнут конец файла (EOF);
口 если ах < сх (но ах ненулевой):
при чтении с устройства — входная строка имеет длину, равную содержимому ах в байтах;
при чтении из файла ——в процессе чтения достигнут EOF.
При чтении с устройства в ах содержится длина считанной строки с учетом завершающего возврата каретки CR (ASCII-код 0DH).
Функция 40Н:
запись данных через описатель
При вызове этой функции сх байт данных записывается в файл или на устройство с описателем, заданным в вх. Данные считываются из буфера, адресуемого через ds:dx, и записываются, начиная с текущей позиции указателя чтения/записи файла. Входными регистрами являются:
口 АН = 40Н;
вх — описатель файла (как в функции 3FH, см. разд. 18.2.3);
口 ds:dx — адрес буфера, содержащего записываемые данные;
сх ——число записываемых байтов.
После вызова функции необходимо сравнивать возвращаемое значение и (число записанных байтов) с сх (запрошенное число байтов для записи):
口 если ах = сх, запись была успешной;
口 если ах < сх, встретилась ошибка (чаще всего переполнение).
Глава 19
Порты 旧 М PC
Компьютер может иметь до четырех поеледовательных портов СОМ1 —— COM4 (в большинстве случаев имеются два порта) с поддержкой на уровне BIOS.
Коммуникационные порты занимают в пространстве ввода/вывода по 8 смежных 8-битовых регистров и могут располагаться по стандартным базовым адресам. Порты могут генерировать аппаратные прерывания.
Конфигурирование портов на плате расширения производится перемычками, а порты на системной плате конфигурируются через BIOS Setup.
В табл. 19.1 для четырех коммуникационных портов представлены базовые адреса, номер IRQ, номер вектора прерывания, базовый адрес порта и адрес тайм-аута порта.
ПримечаниеПримечание
Примечание
Примечание
Таблица 19.1. Ресурсы BIOS коммуникационных портов
Порт
Базовый адрес
IRQ
Вектор прерывания
Базовый адрес порта
Адрес тайм-аута порта (с)
СОМ1
3F8H
IRQ4
(или 11)
INT ОСН
WORD 0:0400
BYTE 0:047С
COM2
2F8H
IRQ3
(или 10)
INT ОВН
WORD 0:0402
BYTE 0:047D
COM3
ЗЕ8Н
WORD 0:0404
BYTE 0:047Е
COM4
2Е8Н
WORD 0:0406
BYTE 0:047F
Для портов COM3 и COM4 возможны альтернативные адреса ЗЕОН, 338Н и 2Е0Н, 228Н соответственно. Для PS/2 стандартными для портов COM3—СОМ8 являются адреса 3320Н, 3228Н, 4220Н, 4228Н, 5220Н, 5228Н соответственно. В PS/2 все порты вызывают прерывание IRQ3.
Важно
Для простоты мы будем указывать все порты относительно базового адреса 3F8H, однако все описанное будет справедливо и для 2FxH.
Порт 3F8H (BasePort+O):
данные и делитель
Этот порт работает по-разному в зависимости от бита DLAB в байте порта 3FBH:
если dlab = о, производятся следующие операции:
запись в регистр tsr 8 битов передаваемого байта;
чтение из регистра rbr 8 битов принимаемого байта;
口 если dlab = 1, то в этот порт пишется младший байт делителя часов, который вместе со старшим байтом (порт 3F9H) составляет 16-битовое значение, задающее скорость передачи (табл. 19.2).
Порт 3F9H (BasePort+1):
разрешение прерываний и делитель
Работа этого порта, также как и порта 3F8H, зависит от бита dlab:
если dlab = о, то в регистре разрешения прерываний ier (Intenupt Enable Register):
биты [7:6] не используются;
бит 5 ——включение режима низкого потребления (Low Power Mode, только микросхема /6750);
бит 4 ——включение 'спящего** режима (Sleep Mode, только микросхема 16750);
бит 3 —- разрешение прерывания по статусу модема (CTS, DSR, R1, RLSD);
бит 2 ——разрешение прерывания по обрыву или ошибке линии;
бит 1 — разрешение прерывания, когда буфер передачи пуст (завершение передачи);
бит 0 — прерывание при готовности принимаемых данных (по приему символа). В режиме FIFO — прерывание по тайм-ауту;
П если dlab = 1, то в порт 3F8H пишется старший байт делителя.
Порт 3FAH (BasePort+2): идентификация прерываний
Если происходит прерывание, то для того, чтобы выяснить причину, следует прочесть регистр идентификации прерываний 工工r (Interrupt Identification Register). Для упрощения анализа UART выстраивает внутренние запросы прерывания по системе приоритетов. Порядок приоритетов (по убыванию) следующий:
О состояние линии;
П прием символа;
口 освобождение регистра передатчика;
Л состояние модема.
При возникновении условий прерывания UART указывает на источник с высшим приоритетом до тех пор, пока он не будет сброшен. Только после этого будет выставлен запрос с указанием следующего источника.
Кроме идентификации прерываний, регистр 工工r содержит биты режима FIFO:
□ биты [7:6] — признак режима FIFO:
11 — режим FIFO 16550А;
10 — FIFO включен, но не используется;
00 — обычный режим;
口 бит 5 — включение 64-байтового буфера FIFO (только для 16750);
口 бит 4 — не используется;
口 бит 3 — прерывание по тайм-ауту приема в режиме FIFO (в буфере есть символы для считывания); для микросхем 8250 и 16450 не используется;
□ биты [2:1] — причина прерывания с наивысшим приоритетом (в обычном, не-FI FO-режиме):
11 — ошибка/обрыв линии, сброс выполняется чтением регистра состояния линии;
10 — принят символ, сброс выполняется чтением данных;
01 ——передан символ (регистр thr пуст), сброс выполняется записью данных;
00 ——изменение состояния модема, сброс выполняется чтением регистра состояния модема;
бит 0 может использоваться при программном опросе и указывает на ожидание прерывания. Если этот бит равен 0, прерывание ожидает, а содержимое hr может использоваться в качестве указателя типа прерывания для программы обслуживания прерывания. Когда он равен 1, ожидающего прерывания нет, и опрос продолжается (если он используется).
В режиме FIFO причину прерывания идентифицируют биты [3:1].
011 — ошибка/обрыв линии. Сброс выполняется чтением регистра состояния линии.
口 010 — принят символ. Сброс выполняется чтением регистра данных приемника.
П 110 — индикатор тайм-аута (за интервал времени, равный времени передачи четырех символов, не передано и не принято ни одного символа, хотя в буфере имеется, по крайней мере, один). Сброс выполняется чтением регистра данных приемника.
П 001 — регистр thr пуст. Сброс выполняется записью данных.
口 000 — изменение состояния модема (CTS, DSR, RI или DCD). Сброс выполняется чтением регистра msr.
19.4. ПортЗЕВН (BasePort+3): управление линией
Регистр управления линией lcr (Line Control Register) имеет следующие биты,
口 Бит 7, dlab ——управление доступом к делителю частоты. После выполнения команды OUT 3FBH, 80Н можно записать в 3F8H младшую часть, а в 3F9H ——старшую часть кода делителя частоты (см. разд. 19.1).
口 Бит 6, brcon (Break Control)——формирование обрыва линии (посыпка нулей) при brcon = 1.
Бит 5, sticpar (Sticky Parity)——принудительное формирование бита паритета:
0 — контрольный бит генерируется в соответствии с паритетом выводимого символа;
1 ——постоянное значение контрольного бита: при even par = 1 — нулевое, при even par = о — единичное.
Бит 4, evenpar (Even Parity Select)——выбор типа контроля:
0 — нечетность;
I — четность.
Бит 3, paren (Parity Enable) — разрешение контрольного бита:
1 —— контрольный бит (паритет или постоянный) разрешен;
0 — контрольный бит запрещен.
Бит 2, stopb (Stop Bits)——количество стоп-бит:
0 — I стоп-бит;
1 — 2 стоп-бита (для 5-битового кода длина будет 1,5).
□ Биты [1:0], serialdb (Serial Data Bits) — количество бит данных:
00 — 5 бит;
01—6 бит;
10 — 7 бит;
11—8 бит.
19.5. Порт 3FCH (BasePort+4): управление модемом
Регистр управления модемом mcr (Modem Control Register).
Биты [7:5] зарезервированы и должны быть равны 0.
Бит 4, lme (Loop Back Mode Enable) — разрешение режима диагностики:
0 — нормальный режим;
1 — режим диагностики.
Бит 3, IE (Interrupt Enable) — разрешение прерываний с помощью внешнего выхода OUT2; в режиме диагностики поступает на вход MSR.7:
0 — прерывания запрещены;
1 — прерывания разрешены.
П Бит 2, outic (OUT1 Bit Control) — управление выходным сигналом 1 (не используется); в режиме диагностики поступает на вход MSR.6.
口 Бит 1, rtsc (Request То Send Control) — управление выходом RTS; в режиме диагностики поступает на вход MSR.4:
0 — активен (―V);
1 — пассивен (+V).
Бит 0, dtrc (Data Terminal Ready Control)——управление выходом DTR; в режиме диагностики поступает на вход MSR.5:
0 — активен (―V);
1 — пассивен (+V).
Порт 3FDH (BasePort+5):
статус линии
Регистр статуса линии lsr (точнее статус состояния приемопередатчика) содержит следующие биты.
口 Бит 7, fifoe (FIFO Error Status) — ошибка принятых данных в режиме FIFO (буфер содержит хотя бы один символ, принятый с ошибкой формата, паритета или обрывом). В не-FIFO-режиме всегда 0.
口 Бит 6, tempt (Transmitter Empty Status) — регистр передатчика пуст (нет данных для передачи ни в сдвиговом регистре, ни в буферных регистрах THR или FIFO). Если установлен в 1, то данных для обработки нет.
口 Бит 5, thre (Transmitter Holding Register Empty) — регистр передатчика готов принять байт для передачи. В режиме FIFO указывает на отсутствие символов в FIFO-буфере передачи. Может являться источником прерывания. В обычном режиме если 1, то можно начинать передачу.
口 Бит 4, bd (Break Detected) — индикатор обрыва линии (вход приемника находится в состоянии 0 не менее чем время посылки символа).
口 Бит 3, fe (Framing Error) — ошибка кадра (неверный стоп-бит).
口 Бит 2, ре (Parity Error) — ошибка контрольного бита (паритета или фиксированного).
口 Бит 1, ое (Overrun Error)——переполнение (потеря символа). Если прием очередного символа начинается до того, как предыдущий выгружен из сдвигающего регистра в буферный регистр или в регистр FIFO, предыдущий символ в сдвигающем регистре теряется и этот бит выставляется в 1.
口 Бит 0, dr (Receiver Data Ready) — принятые данные готовы (в DHR или FIFO-буфере). Сброс — чтением приемника.
Индикаторы ошибок (биты [4:1]) сбрасываются после чтения регистра lsr. В режиме FIFO признаки ошибок хранятся в FIFO-буфере вместе с каждым символом. В регистре они устанавливаются (и вызывают прерывание) в тот момент, когда символ, принятый с ошибкой, находится на вершине FIFO (первый в очереди на считывание). В случае обрыва линии в FIFO заносится только один "обрывной** символ, a UART ждет восстановления и последующего старт-бмта.
Порт 3FEH (BasePort+6): статус модема
Регистр статуса модема msr (Modem Status Register).
Бит 7, dcd (Data Carrier Detect)——состояние линии DCD.
Бит 6, ri (Ring Indicator)—— состояние линии RI.
Бит 5, dsr (Data Set Ready)——состояние линии DSR.
Бит 4, cts (Clear To Send)——состояние линии CTS.
Бит 3, ddcd (Delta Data Carrier Detect) — изменилось состояние линии DCD
Бит 2, teri (Trailing Edge Of Ring Indicator) — спад огибающей (окончание звонка).
Бит 1, ddsr (Delta Data Set Ready) — изменение состояния DSR.
口 Бит 0, dcts (Delta Clear То Send)——изменение состояния CTS.
Биты [7:4] принимают значение 0 при активном состоянии линии (—V) и значение 1 при пассивном состоянии (+V). Биты изменения [3:0] сбрасываются при чтении регистра.
Порт 3FFH (BasePort+7):
заглушка
Порт 3FFH не используется и может отсутствовать в некоторых реализациях.
Скорость передачи
Как описано выше, скорость передачи задается в портах 3F8H и 3F9H при выставленном бите dlab.
Вычисление делителя производится по формуле:
Делитель = (1/16) х Частота^синхронизации / Скорость^_бит/с.
Для IBM PC частота синхронизации составляет 1,8432 МГц, и, следовательно, формула имеет вид:
Делитель = 115 200 / Скоростbjour/с.
Стандартные скорости и их делители приведены в табл. 19.2.
Таблица 19.2. Стандартные скорости и значения делителя
Бит/с
Делитель
3F8H
3F9H
Бит/с
Делитель
3F8H
3F9H
110
1047
04
17
9600
12
00
ОС
150
768
03
00
14 400
8
00
08
300
384
01
80
19 200
6
00
06
600
192
00
СО
28 800
4
00
04
1200
96
00
60
38 400
3
00
03
2400
48
00
30
57 600
2
00
02
4800
24
00
18
115 200
1
00
01
Порты контроллера прерываний
Каждый коммуникационный порт может генерировать аппаратные прерывания. Так, порт СОМ1 генерирует прерывания IRQ4, а COM2 ——прерывания IRQ3.
Для каждого аппаратного прерывания предусмотрен вектор прерывания. Для IRQ4 это вектор ОСН, а для 1RQ3 — вектор ОВН.
Для того чтобы программа могла установить конкретный вектор прерывания, необходимо сначала запретить вызов соответствующих аппаратных прерываний. Для этого требуется задать маску вызова аппаратных прерываний с помощью записи в порт 21Н. Формат порта разрешения аппаратных прерываний показан ниже.
Порт 0021Н (чтение/запись). Регистр разрешения аппаратных прерываний.
口 Бит 7: 0 задает разрешение прерываний принтера.
口 Бит 6: 0 задает разрешение прерываний дисковода.
口 Бит 5: 0 задает разрешение прерываний жесткого диска.
П Бит 4: 0 задает разрешение прерываний СОМ1.
口 Бит 3: 0 задает разрешение прерываний COM2.
口 Бит 2: 0 задает разрешение прерываний видео.
□ Бит 1: 0 задает разрешение прерываний клавиатуры, мыши и таймера реального времени (RTC, Real Time Clock).
口 Бит 0: 0 задает разрешение прерываний таймера.
Таким образом, для разрешения или запрещения прерываний от СОМ1 используется маска ЮН, а для COM2 — маска 08Н.
После самостоятельной обработки аппаратного прерывания необходимо послать контроллеру прерываний сигнал мпрерывание обработано” ——eoi (End Of Interrupt). Для этого в порт 20Н необходимо послать байт следующего формата.
口 Биты [7:5] равны 011, обозначая сигнал eoi.
口 Биты [4:3] зарезервированы и равны 0.
口 Биты [2:0] задают номер IRQ от 0 до 7.
Номера, векторы прерываний и их адреса показаны в табл. 19.3.
Таблица 19.3 Векторы прерываний, их адреса и /RQ
Номер
Вектор
Описание
IRQ
О0020Н
Int 08Н
Таймер, канал 0
IRQ0
00024Н
Int 09Н
Клавиатура
IRQ1
00028Н
Int ОАН
Каскад со вторым ПКП (АТ)
IRQ2
0002СН
Int ОВН
RS-232C, COM2
IRQ3
0003ОН
Int ОСН
RS-232C, СОМ1
IRQ4
00034Н
Int ODH
Жесткий диск PC/XT
IRQ5
00038Н
Int OEH
Контроллер НГМД
IRQ6
0003СН
Int OFH
Параллельный принтер
IRQ7
Глава 20
Структуры Windows для работы с СОМ-портами
Структура настроек порта
COMMCONFIG^
Структура commconfig описывает настройки коммуникационного порта.
Описание структуры на языке С имеет следующий вид:
typedef struct _COMM_CONFIG {
DWORDdwSize;
WORDwVersion;
WORDwRes erved;
DCBdeb;
DWORD dwProviderSubType;
DWORD dwProviderOffset;
DWORD dwProviderSize;
WCHAR wcProviderData[1];
\ азжшта, *1^отжжта;
Описание структуры на языке Delphi имеет вид:
PCommConfig = ATCoinmConfig;
_COMMCONFIG = record
dwSize: DWORD;
wVersion: Word;
wReserved: Word;
deb: TDCB;
dwProviderSubType: DWORD;
I Многие приведенные здесь структуры могут использоваться также для конфигурирования параллельных портов и модемов, но мы будем рассматривать их использование только для СОМ-портов.
dwProviderOffset: DWORD;
dwProviderSize: DWORD;
wcProviderData: array[0. .0] of WCHAR;
end;
TCommConfig = _COMMCONFIG;
Структура commconfig содержит следующие поля.
口 dwsize — должно быть равно размеру структуры.
口 wVersion ——ДОЛЖНО рЗВНЯТЬСЯ 1.
□ wReserved ——не используется.
□ deb — это поле задает описание параметров COM-порта в структуре dcb
(см. разд. 20.5).
口 dwProvide г SubType ——должно быть равно pst_rs232 (значение 1). Полный список возможных значений приводится в табл. 20.2.
口 dwProviderOffser — смещение в байтах от начала структуры до устройство-зависимого блока информации wcProviderData.
□ dwProviderSize —— размер блока wcProviderData В байтах.
□ wcProviderData ——устройство-зависимый блок информации. Это поле может быть любого размера или вообще отсутствовать. Поскольку структура commconfig может быть в дальнейшем расширена, для определения положения данного ПОЛЯ следует использовать dwProviderOffset. Если dwProviderSubType равно PST_RS232 ИЛИ PST_PARALLELPORT, ТО ДЗННОе поле отсутствует. Если dwProviderSubType = PST_MODEM, то данное поле содержит структуру MODEMSETTINGS.
Для установки параметров порта используется функция setcommconfig. Поскольку ручное заполнение полей структуры может быть затруднительно, рекомендуется предварительно считать текущее значение полей с помощью вызова GetcommConfig, затем изменить значение нужных полей и вызвать SetCommConf ig.
Кроме ТОГО, С ПОМОЩЬЮ функции CommConfigDialog МОЖНО ОТОбрИЗИТЬ стандартный диалог Windows для изменения характеристик порта. Эта функция не изменяет характеристики порта, а просто возвращает структуру COMMCONF工G (листинг 20.1).
J1®,r7*,,,v',*svr******'"**—****—,;*;7™ ••*.***■•«*•***■»**"""•■:?***•;'?w*г***?!%;-,“、™у?«-™к,"у™-::FW#e”,*,
Листинг 20Л. Пример использования структуры commconfxg
終“,а “W.-;.
Function TComPort. CommDialog : Boolean;
Var ComCfg : TCommConfig;
Begin
ZeroMemory (QComCfg, SizeOf (TConimConf ig));
ComCfg.dwSize:= SizeOf(TCommConfig);
ComCfg.dcb:= FComProp.DCB;
Result:- CommConfigDialog(PChar(GetComName(False)), 0, ComCfg);
If Result then begin
FComProp.DCB:= ComCfg.deb;
ApplyCcmSettings;
End;
End;
Структура COMMPROP
Структура COMMPROP используется функцией GetCommProperties для получения информации о коммуникационном драйвере. Описание структуры на языке С имеет следующий вид:
typedef struct {
WORD wPacketLength;
WORD wPacketVersion;
DWORD dwSe rvi ceMas k;
DWORD dwReservedl;
DWORD dwMaxTxQueue;
DWORD dwMaxRxQueue;
DWORD dwMaxBaud;
DWORD dwProvSubType;
DWORD dwProvCapabi1ities;
DWORD dwSettableParams;
DWORD dwSettableBaud;
WORD wSettableData;
WORD wSettableStopParity; DWORD dwCurrentTxQueue;
DWORD dwCurrentRxQueue;
DWORD dwProvSpecl;
DWORD dwProvSpec2;
WCHAR wcProvChar[l];
} COMMPROP;
Описание структуры на языке Delphi имеет вид:
PCornmProp = ^TCowmProp;
COMMPROP = record
wPacketLength: Word; wPacketVersion: word; dwServiceMask: DWORD; dwRese rvedl: DWORD; dwMaxTxQueue: DWORD; dwMaxRxQueue: DWORD; dwMaxBaud: DWORD; dwProvSubType: DWORD; dwProvCapabilities: DWORD; dwSettableParams: DWORD; dwSettableBaud: DWORD; wSettableData: Word; wSettableStopParitу: Word; dwCurrentTxQueue: DWORD; dwCurrentRxQueue: DWORD; dwProvSpeс 1: DWORD; dwProvSpec2: DWORD;
wcProvChar: array [0. . 0] of WCHAR; end;
TCoinmProp = _COMMPROP;
Структура commprop содержит следующие поля.
口 wPacketLength — размер пакета (при чтении COM-порта обычно 64).
口 wPacketVersion ——версия структуры (для COM-порта обычно 2).
口 dwServiceMask равно sp_ser工alcomm (значение 1) для СОМ-гюртов.
П dwReservedl ——зарезервировано и не используется.
口 dwMaxTxQueue — максимальный размер внутреннего выходного буфера в байтах. Если 0, то офаничение не используется. Для обычного режима COM-портов вернет 0.
П dwMaxRxQueue — максимальный размер внутреннего входного буфера в байтах. Если 0, то ограничение не используется. Для обычного режима СОМ-порта вернет 0.
dwMaxBaud — максимально доступное значение скорости порта в бит/с. Это поле может принимать одно из значений, приведенных в табл. 20.1. Для COM-порта в обычном режиме вернет baud_user.
dwProvSubType ——режим использования порта. Для СОМ-портов чаще всего равно pst_rs232. В общем случае может вернуть одно из значений, приведенных в табл. 20.2.
dwProvCapabilities ——определяет параметры совместимости порта. Возможные значения этого поля приведены в табл. 20.3.
口 dwSettabieParams ——определяет параметры, которые могут быть изменены. Возможные значения этого поля приведены в табл. 20.4. Обычный COM-порт возвращает маску 127 ($7F).
dwSettabieBaud ——определяет маску допустимых скоростей обмена. Маска СОСТОИТ ИЗ тех же констант, которые использовались В dwMaxBaud Обычно COM-порт возвращает маску 0xl006FFFF.
口 wSettabieData ——определяет маску допустимых форматов байта, приведенных в табл. 20.5. Обычный COM-порт возвращает значение маски ОхОЕ
П wSettableStopParity ——определяет допустимые значения для числа стоп-бит и четности согласно битовой маске, представленной в табл. 20.6. Обычный COM-порт возвращает значение маски S1F07.
dwCurrentTxQueue — размер внутреннего выходного буфера. Значение О свидетельствует о том, что эта величина недоступна.
口 dwCurrentRxQueue — размер внутреннего входного буфера. Значение О говорит о том, что эта величина недоступна.
口 dwProvSpeci, dwProvSpec2, wcProvChar ——величины, зависящие от драйвера. Программы должны игнорировать эти поля.
Таблица 20.九 Константы допустимых скоростей в commprop
Значение
Скорость, 6ит/с
Величина
BAUD/75
75
$00000001
BAUD_110
110
$00000002
BAUD_134_5
134.5
$00000004
BAUD_150
150
$00000008
BAUD_300
300
$00000010
BAUD—600
600
$00000020
BAUD—1200
1200
$00000040
BAUD_1800
1800
$00000080
BAUD_2400
2400
$00000100
BAUD_4800
4800
$00000200
BAUD_7200
7200
$00000400
BAUD_9600
9600
$00000800
BAUD_14400
14 400
$00001000
Таблица 20.1 (окончание)
Значение
Скорость, 6ит/с
Величина
BAUD_19200
19 200
$00002000
BAUD_38400
38 400
$00004000
BAUD_56K
56 000
$00008000
BAUD—57600
57 600
$00040000
BAUD_115200
115 200
$00020000
BAUD_128K
128 000
$00010000
BAUDJJSER
Устанавливается программно
$10000000
Таблица 202 Типы режимов работы порта
Значение
Режим
Величина
PST_FAX
Факс
$021
PST_LAT
Протокол LAT
$101
PST_MODEM
Модем
$006
PST_NETWORK_BR工DGE
Неопределенное сетевое устройство
$100
PST_PARALLE L PORT
Параллельный порт
$002
PST_RS232
Порт RS-232
$001
PST_RS422
Порт RS-422
$003
PST_RS423
Порт RS-423
$004
PST_RS4 4 9
Порт RS-449
$005
PST—SCANNER
Сканер
$022
PST_TCPIP_TELNET
Протоколы TCP/IP и Telnet
$102
PST_UNSPECIFIED
Не определено
$000
PST_X25
Стандарт Х.25
$103
Таблица 20.3. Параметры совместимости порта
Значение
Тип
Маска
PCF_16B工TMODE
Поддерживается специальный 16-битовый режим
$200
Таблица 20.3 (окончание)
Значение
Тип
Маска
PCF—DTRDSR
Поддерживается DTR /DSR
$001
PCF_INTTIMEOUTS
Поддерживается внутренний тайм-аут
$080
PCF_PAR工TY—CHECK
Поддерживается контроль
$008
четности
PCF_RLSD
Поддерживается RLSD
$004
PCF—RTSCTS
Подде ржи вается RTS/CTS
$002
PCF_SETXCHAR
Поддерживается управление XON/XOFF
$020
PCF_S PECIALCHARS
Поддержка специального
$100
символа
PCF_TOTALTIMEOUTS
Поддерживается полный тайм-аут
$040
PCF—XONXOFF
Поддерживается контроль
XON/XOFF
$010
Таблица 20.4. Параметры, доступные для изменения
Значение
Параметр
Маска
SP_BAUD
Скорость передачи
$02
SP_DATABITS
Число бит данных
$04
SP_HANDSHAK工NG
Контроль XON/XOFF
$10
SP_PAR工TY
Четность
$01
S P_PAR工TY_CHECK
Контроль четности
$20
SPRLSD
Сигнал RLSD
$40
SP_STOPBITS
Число стоп-бит
$08
Таблица 20.5. Допустимые форматы данных
Значение
Бит в байте
Маска
DATAB 工 TS_5
5 бит данных
$01
DATAB 工 TS_6
6 бит данных
$02
DATAB 工 TS_7
7 бит данных
$04
Таблица 2。・5 (окончание)
Значение
Бит в байтеМаска
DATAB 工 TS_8
DATABITS_16
DATABITS_16X
8 бит данных$08
16 бит данных$10
Специальное аппаратное$20
расширение
Таблица 20.6. Допустимые значения числа стоп-бит и четности
Значение
ПараметрМаска
STOPBITS_10
1 стоп-бит$0001
STOPBITS_15
STOPBITS_20
1,5 стоп-бита$0002
2 стоп-бита$004
PAR 工TY—NONE
Нет четности$0100
PAR 工 TY_ODD
Нечетность$0200
PARI TY_E VEN
Четность$0400
PAR 工 TY_MARK
Постоянная 1$0800
PAR 工 TY—SPACE
Постоянный 0$1000
Поле wcProvChar имеет переменную длину и может содержать или не содержать данные, поэтому для выделения нужного блока памяти необходимо выполнить следующие шаги (листинг 20.2).
Выделить память под структуру commprop.
Запросить информацию у системы, вызвав функцию GetCommProperties.
Если поле wPacketLength содержит значение, большее, чем sizeof (commprop) , то имеется дополнительная информация. Для ее получения измените размер ранее выделенного блока памяти. Новый размер должен быть равен значению, занесенному системой в поле wPacketLength. Установите в поле wProvSpeci значение commprop _initialized. Это будет означать, что выделен достаточный блок памяти для получения дополнительной информации. Повторно вызовите функцию GetConimProperties.
Впрочем, для обычного коммуникационного порта эта процедура не требуется, т. к. дополнительная информация для него отсутствует. Чаще всего дополнительная информация представлена в виде структуры modemdevcaps, которая размещается на месте поля wcProvChar, если поле dwProvSubType содержит значение pst_modem.
Листинг 20.2. Пример использования структуры commprop
{Получение максимально доступной скорости обмена} function ТСопипРогt.GetMaxBaud: TBaudRate;
var Prop : TCommProp;
begin
Result:= brllO;
{Если нет соединения - ошибка }
if IsConnected then begin
GetCommProperties(Handle, Prop);
case Prop.dwMaxBaud of
BAUD_300 : Result:= ЬгЗОО;
BAUD_600 : Result:= br600;
BAUD_1200,
BAUD_1800 : Result:= brl200;
BAUD_2400 : Result:= br2400;
BAUD一4800,
BAUD_7200 : Result:= br4800;
BAUD_9600:Result:= br9600;
BAUD_工4400 : Result:= brl4400;
BAUD_19200 : Result:= brl9200;
BAUD_38400 : Result:= br38400;
BAUD_56K:Result:= br56000;
BAUD_57600 : Result:= br57600;
BAUD.115200: Result:= brll5200;
BAUD」28K : Result:= brl28000;
BAUD__USER : Result := brCustom;
end;
end else begin
{ Error! }
end;
end;
Структура тайм-аутов
COMMTIMEOUTS
Структура COMMTIMEOUTS используется функциями SetCommTimeouts И GetCoinmTimeouts для установки и получения значений тайм-аутов коммуни-
ЦИЙ ReadFile, WriteFile, ReadFileEx И WriteFileEx При операциях С КОМ- муникационными устройствами. Описание структуры на языке С имеет следующий вид:
typedef struct ССЯММТIMEOUTS {
DWORD ReadlntervalTimeout;
DWORD ReadTotalTimeoutMultiplieг;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD W ri teTotalT imeoutCons t ant;
} CCMMT工MEOUTS, *LPCOMMTIMEOUTS;
Описание структуры на языке Delphi имеет вид:
PCominT ime out s = ^TCcmmTimeouts;
_COMMTIMEOUTS = record
ReadlntervalTimeout: DWORD;
ReadTotalTimeoutMultiplier: DWORD;
ReadTotalTimeoutConstant: DWORD;
WriteTotalTimeoutMultiplier: DWORD;
WriteTotalTimeoutConstant: DWORD;
end;
TConimT imeout s = _CCMMTIMEOUT S;
Структура commtimeouts имеет следующие поля.
П ReadlntervalTimeout ——задает допустимый интервал ожидания в миллисекундах между получением двух символов. Если время ожидания первого символа или время между получением двух символов превышено, функция ReadFiie завершает работу. Значение 0 указывает на отсутствие тайм-аута. Значение maxdword, вместе с нулевыми значениями полей ReadTotalTimeoutConstant И ReadTotalTimeoutMultiplier, означает немедленный возврат из операции чтения с передачей уже принятого символа, даже если ни одного символа не было получено.
口 ReadTotaiTimeoutMuitipiier — задает множитель в миллисекундах, используемый для вычисления полного времени ожидания операции чтения. Для каждой операции чтения это значение умножается на количество запрошенных байтов.
口 ReadTotalTimeoutConstant — задает константу, добавляемую к произведению, полученному при вычислении времени ИЗ ReadTotalTimeoutMultiplier.
口 Поля WriteTotalTimeoutMultiplier И WriteTotalTimeoutConstant аналогичны ПОЛЯМ ReadTotalTimeoutMultiplier И ReadTotalTimeoutConstant
соответственно, но служат для передачи. Задание нулевого значения всех констант Readxxx указывает на отсутствие тайм-аутов чтения, а всех writexxx — отключение тайм-аутов записи.
Пусть, например, заданы такие тайм-ауты:
口 ReadTotalTimeoutMultiplier = 2;
ReadTotalTimeoutConstant = 1;
ReadlntervalTimeout = 1.
Рассмотрим случай считывания 250 символов. Если операция чтения завершится за 250 х 2 + 1 =501 мс, то будет считано все сообщение. Если операция чтения не завершится за 501 мс, то она все равно будет завершена. При этом будут возвращены символы, прием которых завершился до истечения тайм-аута операции. Остальные символы могут быть получены следующей операцией чтения. Операция чтения будет завершена также в случае, если между началом двух последовательных символов пройдет более 1 мс.
ЕСЛИ ПОЛЯ ReadlntervalTimeout И ReadTotalTimeoutMultiplier установлены В MAXDWORD, a ReadTotalTimeoutConstant больше нуля И Меньше MAXDWORD, ТО выполнение операции чтения подчиняется следующим правилам:
если в буфере есть символы, то чтение немедленно завершается и возвращается символ из буфера;
если в буфере нет символов, то операция чтения будет ожидать появления любого символа, после чего она немедленно завершится;
если В течение времени, заданного полем ReadTotalTimeoutConstant, не будет принято ни одного символа, операция чтения завершится по тайм-ауту.
Для получения текущих значений тайм-аутов используется функция GetCommTimeouts, а ДЛЯ установки —— функция S е t CommT ime out s (ЛИСТИНГ 20.3). С ПОМОЩЬЮ вызова BuildCowmDCBAndTimeouts МОЖНО заполнить структуру commtimeouts из строки, формат которой совпадает с форматом команды Mode.
i Листинг 20.3. Пример использования структуры commtimeouts
var
CommTimeOuT: : TCoimmTimeouts;
FillChar(CommTimeOut, SizeOf(TCoramTimeOuts), 0);
CommTimeOut.ReadlntervalTimeout := MAXDWORD;
SetCommTimeOuts (SaveHandle, CaxunTimeOut);
Структура статуса порта COMSTAT
Структура comstat содержит информацию о коммуникационном устройстве. Эта структура заполняется с помощью вызова ciearCommError. Описание структуры на языке С имеет следующий вид:
typedef struct _COMSTAT {
DWORD fCtsHold : 1;
DWORD fDsrHold : 1;
DWORD fRlsdHold : 1;
DWORD fXoffHold : 1;
DWORD fXoffSent : 1;
DWORD fEof : 1;
DWORD fTxim : 1;
CWORD fRe served : 25;
DWORD cblnQue;
DWORD cbOutQue;
} CCMSTAT, *LPCOMSTAT;
Описание структуры на языке Delphi имеет вид:
COMSTAT = record
Flags: TComStateFlags;
Reserved: array[0..2] of Byte;
cblnQue: DWORD;
cbOutQue: DWORD;
end;
TComStat = _COMSTAT;
Структура comstat имеет следующие поля.
0 fCtsHold ——показывает, что производится ожидание сигнала CTS для начала передачи. Если это поле True, передатч и к находится в режиме ожидания.
0 fDsrHold —— показывает, что передатчик находится в режиме ожидания DSR.
fRlsdHold — показывает, что передатчик находится в режиме ожидания RLSD.
0 fXoffHold ——показывает, что передатчик находится в режиме ожидания, т. к. был получен символ xoff.
口 fXoffSend — показывает, что передатчик находится в режиме ожидания, т. к. символ xoff был передан. Передатчик ждет посылки символа xon и игнорирует другие символы.
0 fEof —— показывает, что был получен символ eof.
fTxim — показывает, что передатчик находится в режиме передачи символа функцией TransmitCommChar.
fReserved — не ИСПОЛЬЗуетсЯ.
口 cbinQue —— число байт, полученных коммуникационным драйвером, но еще Не ПрОЧИТаННЫХ С ПОМОЩЬЮ ReadFile.
口 cbOutQue ——число байт, переданных функциями передачи, но еще не отправленных. Это поле будет нулевое для синхронного режима передачи.
Чаще всего используют поле cbinQue. Это поле показывает число байт в приемном буфере. Структура comstat может быть считана вызовом функции ciearCommError, которая сбрасывает флаги ошибки, если таковые были, но записывает текущее состояние порта в структуру comstat (листинг 20.4).
产..„0…丁….,3学 K,.:¥,.".:...餐.安訳萃J?*■•,,/登:……"七发次:,,....,..^.„..—. *"* ….""…%.
;Листинг 20,4. Пример использования структуры comstat
// Возвращает число байт в приемном буфере function ТСопипРогt.CountRX: DWORD;
var Stat : TCOMSTAT; Errs: DWORD;
begin
Result:= 65535; // ошибка
if not Connected then Exit; // порт не открыт
// Получение числа байт в буфере
ClearComraError(FHandle, Errs, @Stat );
Result:= Stat.cbinQue;
end;
Структура DCB
Структура dcb (Device Control Block, блок управления данными) содержит управляющие настройки коммуникационного устройства. Описание структуры на языке С имеет следующий вид:
typedef struct _DCB { DWORD DCBlength; DWORD BaudRate;
DWORD fBinary: 1; DWORD fParity: 1; DWORD fOutxCtsFlow:1; DWORD fOutxDsrFlow:1; DWORD fDtrControl:2; DWORD fDsrSensitivity:1;
DWORD
fTXContinueOnXoff:1;
WORD
fOutX: 1;
DWORD
flnX: 1;
DWORD
fErrorChar: 1;
DWORD
fNull: 1;
DWORD
fRtsControl:2;
DWORD
fAbortOnError:1;
DWORD
fDummy2:17;
WORD
wReserved;
WORD
XonLim;
WORD
XoffLim;
BYTE
ByteSize;
BYTE
Parity;
BYTE
StopBits;
char
XonChar;
char
XoffChar;
char
ErrorChar;
char
EofChar;
char
EvtChar;
WORD
DCB;
wReservedl;
Описание структуры на языке Delphi имеет вид:
DCB = packed
record
DCBlength :
DWORD;
BaudRate :
DWORD;
Flags:
Longint;
WReserved :
Word;
XonLim:
Word;
XoffLim :
Word;
ByteSize :
Byte;
Parity:
Byte;
StopBits :
Byte;
XonChar :
CHAR;
Xof fChar :
CHAR;
ErrorChar :
CHAR;
Eo fChar :
CHAR;
EvtChar :
CHAR;
wReservedl: Word;
end;
TDCB = _DCB;
Структура dcb имеет следующие поля.
DCBiength — содержит длину структуры. См. пример в разд. 21.6.
BaudRate ——задает скорость обмена. Может задаваться либо числом (в бит/с), либо одной из констант табл. 20.7.
fBinary — до刀*но быть True, т. к. Windows поддерживает только бинарный метод доступа.
fParity — включает режим контроля четности. Если поле True, выполняется контроль четности.
fOutxCtsFlow — включает управление выводом с помощью сигнала CTS. Если это поле True и CTS сброшен, то вывод приостанавливается до установки сигнала CTS.
fOutxDsrFiow — включает управление выводом с помощью сигнала DSR. Если это поле True и DSR сброшен, то вывод приостанавливается до установки сигнала DSR.
fDtrControi ——может принимать одно из значений.
DTR CONTROL D工sable ——запрещает использование сигнала DTR.
dtr_control_enable — разрешает использование сигнала DTR. Конкретное значение сигнала можно задавать через вызов ЕscapeCommFunction.
dtr_control_handshake — автоматическое управление DTR. Вызов функции EscapeCommFunction вызовет ошибку. ЭТОТ реЖИМ ИСПОЛЬЗу- ется, например, модемами при восстановления потерянной связи.
fDsrSensitivity —— включает восприятие DSR сигнала. Если это поле True, то порт игнорирует все принимаемые данные, пока не будет установлен сигнал DSR.
fтхсопtinueOnXoff ——задает, прекращается ли передача при переполнении приемного буфера и передаче драйвером символа XoffChar.
Если это поле равно True, то передача продолжается, несмотря на то, что приемный буфер содержит более xof £Lim символов и близок к переполнению. а драйвер передал символ XoffChar для приостановления потока принимаемых данных.
Если поле равно False, то передача не будет продолжена до тех пор, пока в приемном буфере не останется меньше xonLim символов и драйвер не передаст символ xonChar для возобновления потока иринимае-
мых данных. Таким образом, это поле вводит некую зависимость между управлением входным и выходным потоками информации.
О foutx — включает режим xon / хо ff- передач и. Если это поле True, передача останавливается по получении символа xoffChar и возобновляется при получении символа XonChar.
П finx ——включает режим xoN/xoFF-приема. Если это поле True, символ xoffChar посылается при заполнении входного буфера до предела xoffLim байт, а символ XonChar посылается, когда буфер опустошен до размера XonLim.
fErrorChar — разрешает замещение ошибочных символов. Если это поле true и поле fParity = True, то ошибочные символы будут заменены СИМВОЛОМ ErrorChar.
П fNuii — включает игнорирование нулевых байтов при приеме. Если это поле True, нулевые байты будут игнорироваться при получении.
П fRtsControi —— задает режим управления линией RTS. Может принимать одно из значений.
rts_control_d工sable — запрещает использование линии RTS.
rts_control_enable — разрешает использование линии RTS. Конкретное значение RTS может быть задано с помощью вызова функции Е s cape ConimFunct ion.
rtS—COntroL—Handshake ——включает автоматическое управление линией RTS. Драйвер устанавливает RTS в 1, когда входной буфер менее чем на половину пуст, и в 0, когда буфер заполнен более чем на три четверти. Вызов EscapeCommFunction вызовет ошибку.
rts_control_toggle (только для Windows NT/2000/XP)——задает режим управления, когда RTS будет в I, если есть байты для передачи. RTS сбросится в 0 после передачи всех байтов буфера. Вызов функции EscapeCommFunсtion приведет к ошибке. Этот режим не работает в Windows 95/98/МЕ.
П fAbortOnError — прекращает операции чтения/записи при возникновении ошибок. Драйвер порта не будет воспринимать любые операции чтения/записи ДО тех пор, пока не будет вызвана функция ClearCommError.
П fDummy2 И wReserved —— не ИСПОЛЬЗуЮТСЯ. wReserved ДОЛЖНО быть установлено В 0.
П xonLim задает — минимальное число символов в приемном буфере перед посылкой символа xon.
П xoffLim — определяет максимальное количество байтов в приемном буфере перед посылкой символа xoff. Максимально допустимое количест-
во байтов в буфере вычисляется вычитанием данного значения из размера приемного буфера в байтах.
П Bytesize ——задает число бит в байте. Принимает значения от 5 до 8.
Parity — задает проверку паритета:
noparity — нет проверки, бит четности отсутствует;
oddparity ——проверка нечетности, бит дополняет до нечетности;
evenparity ——проверка четности, бит дополняет до четности;
markparity — фиксировано 1, бит четности всегда 1;
spaceparity — фиксировано 0, бит четности всегда 0.
stopBits — задает число стоп-бит:
ONESTOPBIT — ОДИН СТОП-611Т;
one5stopbits —— 1,5 стоп-бита (только для 5- и 6-битовых данных);
twostopbits — 2 стоп-бита.
口 XonChar, xoffChar —— задают начальный и конечный символы для режи-мов fTXContinueOnXoff = True, ИЛИ fInX = True, ИЛИ fOutX = True.
ErrorChar ——Задает символ-замену при условии fErrorChar = True.
Eofchar — задает символ конца данных.
口 Evtchar ——задает символ для вызова события.
wReservedl ——не ИСПОЛЬЗуетСЯ.
Таблица 20.7. Скорости обмена
Константа
Значение
Величина
CBRJ10
110
110
CBR_300
300
300
CBR_600
600
600
CBR_1200
1200
1200
CBR_2400
2400
2400
CBR_4800
4800
4800
CBR_9600
9600
9600
CBR_14400
14 400
14 400
CBR_19200
19 200
19 200
CBR_38400
38 400
38 400
CBR_5600
56 000
56 000
Таблица 20.7 (окончание)
Константа
ЗначениеВеличина
CBR—57600
CBRJ 15200
CBR_128000
57 60057 600
115 200S1C200
128 000$1F400
CBR_256000
256 000$ЗЕ800
Важно отметить, что программист должен сам следить за совместимостью параметров блока dcb. Например:
D Bytesize может задаваться только от 5 до 8;
комбинация Bytesize = 5 и 2 стоп-бита не допустима, так же, как 1,5 стоп-бита для 6-, 7- и 8-битовых данных.
Для записи структуры DCB применяется функция SetCommState, а для чтения ——функция GetCommState.
Если заполнение многочисленных полей этой структуры вызывает затруднение, МОЖНО воспользоваться функцией BuildCommDCB (или функцией BuildConunDCBAndTimeouts), заполняющей ее поля, согласно строке настроек. Формат строки совпадает с форматом команды Mode. Также можно сначала прочитать текущее состояние с помощью GetCoimrLstate, изменить нужные ПОЛЯ структуры, а затем ВЫПОЛНИТЬ запись С ПОМОЩЬЮ SetCommState (листинг 20.5).
京т,"・幻蟹 ук‘—阡 WF'...*....,•:*产.…貰…二:"… ■—.•■■'
Листинг 20.5. Пример использования структуры dcb
var
DCB: TDCB;
GetCommState (FComPort. FCcmmThread. ComHandle, DCB);
DCB. Flags := FFlags;
SetCommState (FComPort. FCoinmThread. ComHandle, DCB);
В Delphi, как видно из описания структуры, битовые поля заменены на одно поле Flags. Для задания конкретных бит флагов используются константы, описанные в листинге 20.6.
^ Листинг 206 Константы для поля Flags структуры DCB (Delphi)
Const
dcb Binary= $00000001;
dcb_
_ParityCheck
=
$00000002;
dcb_
_OutxCtsFlow
=
$00000004;
deb
_OutxDsrFlow
$00000008;
dcb_
DtrControlMask
=
$00000030;
deb
_DtrControlDisable
=
$00000000;
dcb_
DtrControlEnable
=
$00000010;
dcb_
_DtrControlHandshake
=
$00000020;
dcb_
DsrSensivity
=
$00000040;
dcb_
TXContinueOnXoff
=
$00000080;
dcb_
OutX
$00000100;
dcb_
_InX
=
$00000200;
dcb_
_ErrorChar
=
$00000400;
deb
NullStrip
=
$00000800;
dcb_
RtsControlMask
=
$00003000;
dcb_
RtsControlDisable
=
$00000000;
dcb_
RtsControlEnable
=
$00001000;
dcb_
RtsControlHandshake
=
$00002000;
dcb_
RtsControlToggle
=
$00003000;
dcb_
_AbortOnError
=
$00004000;
dcb_
Reserveds
=
$FFFF8000;
Глава 21
Функции Windows для работы с СОМ-портами
Важно
В Windows для работы с коммуникационными портами используются те же функции, что и для работы с файлами. В нашей книге для таких функций мы опишем параметры, относящиеся только к работе с портами. Описание остальных параметров вы можете найти, например, в MSDN.
Функции CreateFile и CloseHandle: открытие и закрытие порта
Функция CreateFile открывает Порт, а фуНКЦИЯ cioseHandie закрнвает. Если процесс попытается открыть ресурс, уже открытый другим процессом, функция CreateFile вернет ошибку. Дескриптор, полученный после вызова CreateFile, Должен быть закрыт После использования ВЫЗОВОМ CloseHandle.
cioseHandle на языке С имеет следующийcioseHandle на языке С имеет следующийФормат заголовков CreateFile вид:
cioseHandle на языке С имеет следующий
cioseHandle на языке С имеет следующий
HANDLE CreateFile (
LPCTSTR IpFileName,
DWORDdwDesiredAccess,
DWORDdwShareMode,
LJSECUR工TYATTRIBUTES IpSA,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes r
HANDLE hTemplateFile
// имя файла (порта)
// способ доступа к файлу
// тип совместного доступа
// атрибуты защиты
// параметры создания файла
// атрибуты файла
// дескриптор template-файла
);
// дескриптор порта// дескриптор портаBOOL CloseHandle ( HANDLE hObj ect );
// дескриптор порта
// дескриптор порта
CloseHandleCloseHandleна языке Delphi имеет еле-на языке Delphi имеет еле-
CloseHandle
CloseHandle
на языке Delphi имеет еле-
на языке Delphi имеет еле-
Формат заголовков CreateFile И
дующий вид:
function CreateFile( IpFileName: PChar; dwDesiredAccess, dwShareMode: DWORD; IpSA : PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD; hTemplateFile: Thandie
):THandle;
function CloseHandle( hObj ect: THandle
// имя файла (порта)
// способ доступа к файлу
// тип совместного доступа
// атрибуты защиты
// параметры создания файла
// атрибуты файла
// дескриптор template-файла
// дескриптор порта
):BOOL;
Для использования функции CreateFile в приложении к коммуникацион- ному порту должны быть выполнены несколько условий.
口 В качестве имени порта должна передаваться строка вида \\.\С0М1, \ \. \C0M2 и т. д. Для портов с номерами COMI—СОМ9 могут использоваться простые имена COMI—СОМ9 без префиксов, а для СОМ10 и далее необходимо указание префикса.
口 Способ доступа должен быть задан явно и установлен в значение generic_read (порт используется только для чтения данных), или gener工c_wr工те (порт используется только для записи данных), или GENERIC-READ or GENERIC_WRITE (порт ИСПОЛЬЗуеТСЯ И ДЛЯ ЧТеНИЯ, И ДЛЯ записи данных).
口 Параметр dwSha reMode у ста н а вл и вается в значение 0, означающее "общий доступ к ресурсу запрещен*1, т. к. коммуникационные порты нельзя делать разделяемыми ресурсами.
П Атрибуты защиты не используются и устанавливаются в nil.
口 Параметр создания файла dwCreationDisposition устанавливается в значение OPEN EXIST工ng, указывая открыть ресурс, если он существует, или вернуть ошибку, если не существует. Другие значения этого параметра не допускаются.
口 Атрибуты для порта должны быть установлены в значение FILE_ATTRIBUTE_NORMAL ДЛЯ СИНХРОННОГО И В значение FILE_ATTRIBUTE_NORMAL or FILE _FLAG_OVER LАР PED ДЛЯ ЗСИНХрОННОГО доступа к порту;
П Последний параметр обязательно должен иметь значение nil.
Дополнительные сведения
Для нотации языка С строка, передаваемая в функцию CreateFiie, будет выглядеть как \\\\. \\comi.
Функция CreateFiie может применяться для открытия файла драйвера, например, мы использовали ее для загрузки драйвера прямого доступа к портам giveio (см. разд. 9.3〃.
В Windows NT и выше в качестве имени порта можно использовать любое DOS-имя, связанное с устройством (см. разд. 13.4).
Возвращаемое значение
Если функция CreateFiie выполнена успешно, возвращается дескриптор открытого ресурса. Этот дескриптор используется для доступа к открытому ресурсу. Если при открытии ресурса произошла ошибка, функция возвращает значение inval工d_handle_ualue, а подробности можно узнать, вызвав ФУНКЦИЮ Get La st Error.
После использования дескриптор должен быть освобожден вызовом функции CloseHandle.
Функция CloseHandle возвращает ненулевое значение, если закрытие порта выполнено, и возвращает 0, если произошла ошибка. Расширенную информацию об ошибке МОЖНО получить С ПОМОЩЬЮ вызова GetLastError.
Пример вызова
Листинг 21.1 показывает структуру программы, использующей функции CreateFile/closeHandle ДЛЯ доступа К коммуникационному порту СОМ1.
;Листинг 21・1・ Использование функций CreateFiie и CloseHandle
{Переменная для хранения дескриптора порта} var ComHandle : THandle;
(Открыть порт}
ComHandle:= CreateFiie (1 \\. \COM11,
GENER工C_READ or GENERIC_WRITE, 0, nil, OPEN—EX 工 ST 工 NG,
FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0
);
{Проверить результат}
if ComHandle = INVALID_HANDLE_VALUE then begin
{Ошибка открытия порта, функция GetLastError вернет код ошибки} Exit;
end;
{... порт открыт успешно ...}
{... использование порта через дескриптор ComHandle ...}
{Закрытие порта}
CloseHandle(ComHandle);
Функция ReadFile: чтение данных из порта
Функция ReadFile производит синхронное или асинхронное чтение данных из файла (порта).
Формат заголовка ReadFile на языке С имеет следующий вид:
BOOL ReadFile(
HANDLE
LPVOID
hHandle,
IpBuffer.
// дескриптор, полученный от CreateFile // буфер для чтения
DWORD
nNBTR,
// число байт для чтения
LPDWORD
nNBR,
// реально прочитанное число байт
LPOVERLAPPED
IpOverlapped
// параметры асинхронного чтения
Формат заголовка ReadFile на языке Delphi имеет следующий вид:
function ReadFile(
hFile: THandle; // var Buffer; // nNuniberOfBytesToRead: DWORD; // var IpNumberOfBytesRead: DWORD;// IpOverlapped: Poverlapped //hFile: THandle; // var Buffer; // nNuniberOfBytesToRead: DWORD; // var IpNumberOfBytesRead: DWORD;// IpOverlapped: Poverlapped //дескриптор, полученный от CreateFile буфер для чтения число байт для чтения реально прочитанное число байт параметры асинхронного чтениядескриптор, полученный от CreateFile буфер для чтения число байт для чтения реально прочитанное число байт параметры асинхронного чтения
hFile: THandle; // var Buffer; // nNuniberOfBytesToRead: DWORD; // var IpNumberOfBytesRead: DWORD;// IpOverlapped: Poverlapped //
hFile: THandle; // var Buffer; // nNuniberOfBytesToRead: DWORD; // var IpNumberOfBytesRead: DWORD;// IpOverlapped: Poverlapped //
дескриптор, полученный от CreateFile буфер для чтения число байт для чтения реально прочитанное число байт параметры асинхронного чтения
дескриптор, полученный от CreateFile буфер для чтения число байт для чтения реально прочитанное число байт параметры асинхронного чтения
):BOOL;
Первый параметр передает дескриптор порта, полученный с помощью функции CreateFile. Указатель на буфер для чтения данных задается с по-
м。山ью второго параметра, а размер зтого буфера в байтах — с помощью третьего.
Параметр nNBR передает указатель на переменную типа dword, в которую возвращено реально прочитанное число байт. В Windows NT/2000/XP этот параметр не может быть null, если ipOve г lapped равно null и может быть null, если параметр ipOveriapped ненулевой. В Windows 95/98/МЕ этот параметр не может быть нулевым. Для получения количества байт, прочитанных в асинхронном режиме, может использоваться функция GetOverlappedResult.
Последний параметр передает настройки для асинхронного чтения данных. Если порт был открыт с параметром f工le_flag_overlapped, этот параметр обязательно должен указывать на правильную структуру типа overlapped, если же порт был открыт без использования file_flag_overlapped, то этот указатель обязательно должен быть null.
Дополнительные сведения
В Windows NT/2000/XP можно использовать функцию ReadFileEx для асинхронного чтения данных (см. разд. 21.4).
Так как ReadFiie требует распределения буфера перед чтением данных, то можно воспользоваться функцией ciearCommError для получения числа доступных для чтения байтов.
Возвращаемое значение
Функция завершается, если прочитано необходимое количество байтов или произошла ошибка. Если чтение прошло успешно, возвращается ненулевое значение.
В случае ошибки возвращается 0, а код ошибки можно получить с помощью ВЫЗОВа GetLastError.
Перед чтением необходимо установить тайм-ауты ожидания с помощью вызова SetCommTimeouts.
Пример вызова
Листинг 21.2 показывает пример использования функции ReadFiie для синхронного чтения данных. Перед чтением данных вызывается функция ciearCommError для получения числа символов, доступных для чтения.
Листинг 24>2. Пример использования функции ReadFiie
var ComHandle : THandle;
Currentstate : TComStat;
CodeError : Cardinal;
PData : Pointer;
AvaibleBytes, RealRead : Cardinal;
Begin
ComHandle:= CreateFile(...);
{Возвращает структуру состояния порта и код ошибок} ClearCommError(ComHandle, CodeError, @CurrentState); { Число полученных, но еше не прочитанных байт } AvaibleBytes:= Currentstate.cblnQue;
{ Проверка числа доступных байт }
If AvaibleBytes > 0 then begin GetMem(PData, AvaibleBytes);
If ReadFile(CcmHandle, PData^, AvaibleBytes, RealRead, nil) then begin {Реально прочитано RealRead байт}
End;
FreeMem(PData);
End;
CloseHandle(ComHandle);
end;
Функция WriteFile:
передача данных
Функция writeFile производит синхронную или асинхронную запись данных в файл (порт).
Формат заголовка WriteFile на языке С имеет следующий вид:
BOOL WriteFile(
HANDLE
hHandle,
// дескриптор, полученный от CreateFile
LPCVOID
IpBuffer,
// буфер данных
DWORD
nNBTW,
// ллина буфера
LPDWORD
nNBW,
// реально отправленное число байт
LPOVERLAPPED
IpOverlapped
// параметры асинхронной записи
);
Формат заголовка writeFile на языке Delphi имеет следующий вид:
function WriteFile (
hFile: THandle;
// дескриптор, полученный от CreateFiie
const Buffer;
// буфер данных
nNBTW: DWORD;
// длина буфера
var IpNBW : DWORD;
// реально отправленное число байт
IpOveriapped: POverlapped
// параметры асинхронной записи
):BOOL;
Первый параметр передает дескриптор порта, полученный с помощью функции CreateFiie. Указатель на буфер данных для записи задается с помощью второго параметра, а размер этого буфера в байтах — с помощью третьего.
Параметр nNBW задает указатель на переменную типа dword, в которую будет записано реально переданное число байтов. В Windows 95/98/МЕ этот параметр не может быть нулевым. В Windows NT/2000/XP этот параметр может быть нулевым, если задан указатель на параметры асинхронной записи ipOve г lapped, и не может быть нулевым, если задается синхронная запись, т. е. указатель ipOveriapped нулевой.
Дополнительные сведения
В Windows NT/2000/XP МОЖНО использовать функцию WriteFileEx для асинхронной записи данных (см. разд. 21.5).
Возвращаемое значение
Если выполнение успешно, функция WriteFile возвращает ненулевое значение. Если функция завершилась с ошибкой, она возвращает нулевое значение, а КОД ошибки МОЖНО узнать С ПОМОЩЬЮ вызова GetLastError.
Пример вызова
Листинг 21.3 показывает пример использования функции WriteFile для синхронной записи данных в коммуникационный порт, а листинг 21.4 — пример асинхронной записи.
Листинг21;3. Пример использования функции WriteFile (синхронная запись) j
var
FCcmPortHandle : THANDLE;
DataPtr : Pointer;
nToWrite, nWrite : Integer;
FComPortHandle: = CreateFile (...);
nToWrite:= количество передаваемых байт
GetMem(DataPtr, nToWrite);
... заполняем буфер данными ...
WriteFile(FComPortHandle, DataPtr^, nToWrite, nWrite, nil);
FreeMem(DataPtr);
CloseHandle(FComPortHandle);
吧吧 214 吧м° 吧”史у”吧史—асинхронная запись)
var
FComHandle : THandle;
AsyncPtr : PAsync;
BytesTrans : DWORD;
FCcmHandle:= CreateFile(...,
FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0
{Создание асинхронных параметров}
New(AsyncPtr);
With AsyncPtr"4 do begin
Kind := 0; { 0 — write, 1 — read }
GetMem(Data, Count);
Move(Buffer, Data^, Count);
Size := Count;
end;
{Передача данных}
WriteFile(FComHandle, Buffer, Count, BytesTrans, @AsyncPtrA.Overlapped);
{Освобождение памяти}
Dispose(AsyncPtr);
CloseHandle(FComHandle);
Функция ReadFileEx:
АРС-чтение данных
Функция ReadFileEx осуществляет асинхронное чтение данных. Работа этой функции похожа на ВЫЗОВ обычной функции ReadFile в режиме асинхрон-
ного чтения, но ReadFiieEx позволяет программе выполнять другие действия во время чтения данных. При завершении чтения будет вызвана специальная callback-процедура.
В Windows 95/98/МЕ эта функция не может быть использована для чтения данных из СОМ-порта.
Формат заголовка ReadFiieEx на языке С имеет следующий вид:
BOOL ReadFiieEx (
HANDLEhFile,//дескриптор порта
LPVOIDIpBufferf//буфер для данных
DWORDdwBufLen,//число байт для чтения
LPOVERLAPPED IpOveг1apped, // параметры асинх. чтения // callback-процедура, выполняемая по завершении чтения LP_OVERLAPРЕD_COMPLETION_ROUTINE IpComletionRoutine );
Формат заголовка ReadFiieEx на языке Delphi имеет следующий вид:
function ReadFiieEx (
hFile: THandle;
IpBufferIpBuffer:Pointer;:Pointer;дескриптор порта буфер для данных число байт для чтениядескриптор порта буфер для данных число байт для чтенияIpOve г lapped:// са 1 Iback-процедура,IpOve г lapped:// са 1 Iback-процедура,POverlapped;POverlapped;выполняемаявыполняемая////попопараметры асинх. чтенияпараметры асинх. чтениязавершении чтениязавершении чтенияnNumberOfBytesToRead: DWORD;
IpBuffer
IpBuffer
:Pointer;
:Pointer;
дескриптор порта буфер для данных число байт для чтения
дескриптор порта буфер для данных число байт для чтения
IpOve г lapped:
// са 1 Iback-процедура,
IpOve г lapped:
// са 1 Iback-процедура,
POverlapped;
POverlapped;
выполняемая
выполняемая
//
//
по
по
параметры асинх. чтения
параметры асинх. чтения
завершении чтения
завершении чтения
IpCompletionRoutine : ТPROveг1appedComplet ionRoutine ):BOOL;
Все параметры, кроме последнего, совпадают с параметрами функции ReadFiie. Последний параметр задает адрес процедуры, которая будет выполнена по завершении чтения данных. Эта процедура должна иметь тип TPROverlappedCompletionRoutine, ОПИСЫВЭСМЫЙ следующим образом:
VOID CALLBACK Fi 1 еIOCoir.plеtionRoutine (
DWORDDWORDDWORDDWORDdwErrorCode,dwNumberOfBytesTransfered,dwErrorCode,dwNumberOfBytesTransfered,LPOVERLAPPED IpOve г 1 appedLPOVERLAPPED IpOve г 1 apped// код ошибки// число прочитанных байт // асинхронная структура// код ошибки// число прочитанных байт // асинхронная структура
DWORD
DWORD
DWORD
DWORD
dwErrorCode,
dwNumberOfBytesTransfered,
dwErrorCode,
dwNumberOfBytesTransfered,
LPOVERLAPPED IpOve г 1 apped
LPOVERLAPPED IpOve г 1 apped
// код ошибки
// число прочитанных байт // асинхронная структура
// код ошибки
// число прочитанных байт // асинхронная структура
обычный указатель, без специфи-обычный указатель, без специфи-В Delphi тип этой процедуры описан как кации параметров:
обычный указатель, без специфи-
обычный указатель, без специфи-
type
TPROverlappedConpletionRoutine = TFarProc;
На самом деле формат заголовка этой процедуры в Delphi должен быть такой, как показан в листинге 21.5.
Параметры callback-процедуры имеют следующий смысл:
□ параметр dwErrorCode принимает значение 0, если операция успешна;
口 параметр dwNumberOfBytesTransfeted равен ЧИСЛУ Прочитанных баЙТОВ или 0, если функция завершена с ошибкой;
口 параметр ipOve г lapped передает структуру асинхронного чтения.
Возвращаемое значение
При успешном завершении функция ReadFiieEx возвращает ненулевое значение, а при ошибочном ——ноль, при этом код ошибки можно получить с ПОМОЩЬЮ вызова GetLastError.
Дополнительные сведения
Для завершения всех асинхронных операций может использоваться функция Cancello.
Функция ReadFileEx игнорирует параметр hEvent В структуре IpOveг lapped, и он может использоваться программой.
Пример вызова
Листинг 21.5 показывает пример использования ReadFileEx. Обратите внимание, что процедура, вызываемая при завершении операции, должна иметь спецификатор stdcali.
:•'.”*?:.:.:富笠.人产£”'","門"「】'\芹¥'•*"'«. l:;・.”Yf5'.■:■.".♦'■..¥."5:F*"‘‘r*«"."''!!"* ■
Глистинг 21.5. Пример использования функции ReadFileEx
:“二""二"""4":"艺:*…3鑫W. <•««.'-:訖:弘-C.金■;J:"二二’/….;1工::-.二 二.…W у…aзJ…」与上"……
Procedure TReadThread.Execute;
Var ReadOL : TOverLapped; {структура для асинхронного чтения}
{Callback-процедура, вызывающаяся при получении байта} Procedure OnCompletionRead( dwErrorCode, dwNumberOfBytesTransfered :Cardinal; var IpOverlapped : TOverlapped );stdcali; begin end;
Begin
With FOwner do
While (not Terminated) and Connected do begin {пока порт открыт}
{Запуск операции асинхронного чтения}
ReadFileEx(FHandle, ©FByte, 1, ©ReadOL, GOnCompletionRead);
{Ожидание завершения операции}
SleepEx(INFINITE, True);
{Сюда мы попадем, только когда байт будет принят}
Synchronize(DoReadByte);
End;
End;
Функция WriteFileEx: АРС-передача данных
Функция WriteFiieEx производит асинхронную запись данных. Работа этой функции похожа на вызов функции WriteFiie в режиме асинхронной записи, но WriteFiieEx позволяет программе выполнять другие действия во время записи (передачи) данных. При завершении записи будет вызвана специальная callback-процедура.
В Windows 95/98/МЕ эта функция не может быть использована для чтения данных из СОМ-порта.
Формат заголовка WriteFiieEx на языке С имеет следующий вид:
BOOL WriteFiieEx (
HANDLEhFile,//дескриптор порта
LPCVOIDIpBuffer,//буфер для данных
DWORDdwBufLen,//число байт для записи
LPOVERLAPPED IpOveг 1 apped, // параметры асинх. записи
// са11Ьасk-процедура,* выполняемая по завершении записи
LP_OVERLAPPED__CCMPLETION_ROUTINE IpComletionRoutine );
Формат заголовка WriteFiieEx на языке Delphi имеет следующий вид: function WriteFiieEx (
hFile: THandle;// дескриптор порта
IpBuffer : Pointer;// буфер для данных
nNuniberOfBytesToWrite: DWORD; // число байт для записи const IpOveгlapped : TOveгlapped; // параметры асинх. записи IpCompletionRoutine: EARPROC
):BOOL;
Все параметры этой функции совпадают с параметрами функций writeFiie И ReadFileEx.
Возвращаемое значение
При успешном завершении функция writeFiieEx возвращает ненулевое значение, а при ошибочном — ноль. Код ошибки можно получить с помощью вызова GetLastError.
Пример вызова
Листинг 21.6 показывает пример использования WriteFiieEx. Обратите внимание на спецификатор stdcall функции обратного вызова OnCompletionWrite.
Листинг 21.6. Пример использования функции WriteFiieEx
Function TComPort.WriteByte(const В : Byte) : Boolean;
Var WriteOL : TOverLapped; {структура для асинхронной записи} {Callback-процедура, вызываемая после завершения передачи} Procedure OnCompletionWrite( dwErrorCode, dwNumberOfBytesTransfered : Cardinal; var IpOver1apped : TOverlapped );stdcall; begin
MessageBeep(0); end;
Begin
Result:= False;
{создание события для асинхронной записи}
FillChar(WriteOL, SizeOf(WriteOL), 0);
WriteOL.hEvent:= CreateEvent(nil. True, True, nil);
{асинхронная отправка байта}
WriteFiieEx(FHandle, @B, 1, WriteOL, @OnCompletionWrite);
SleepEx(INFINITE, True);
{освобождение дескриптора события} CloseHandle(WriteOL.hEvent);
End;
Функция BuildCommDCB' создание структуры DCB из строки
Функция BuildCommDCB помогает заполнить многочисленные поля структуры dcb согласно строке, имеющей формат команды mode.
Формат заголовка BuildCommDCB на языке С имеет следующий вид:
BOOL Bui IdCommDCB (
LPCTSTR IpDef, // строка
LPDCB IpDCB // указатель на блок параметров DCB
);
Формат заголовка BuildCoromDCB на языке Delphi имеет следующий вид:
function Bui 1 dCommDCB (
IpDef: PChar; // строка
var IpDCB: TDCB // указатель на блок параметров DCB
):ВСЮЬ;
Структура dcb заполняется согласно параметрам, описанным в строке описания IpDef. Строка описания должна иметь формат как для команды Mode, например,
baud=1200 parity=N data=8 stop=l
COM1: baud=1200 parity=N data=8 stop=l
Обычно функция BuldCommDCB изменяет только явно перечисленные в строке IpDef поля. Однако существуют два исключения из этого правила.
При задании скорости обмена 110 бит в секунду автоматически устанавливается формат обмена с двумя стоп-битами. Это сделано для совместимости с командой mode из MS-DOS или Windows NT.
По умолчанию запрещается программное (xon/xoff) и аппаратное управление потоком. Необходимо вручную заполнить требуемые поля dcb, если требуется управление потоком.
Функция BuiicommDCB поддерживает как новый, так и старый форматы командной строки mode. Однако нельзя смешивать эти форматы в одной строке. Новый формат строки позволяет явно задавать значения для полей dcb, отвечающих за управление потоком. При использовании старого формата необходимо учитывать следующие соглашения:
口 для строк вида 9600,п,8,1 (не заканчиваюшихся символами их' или ”р”):
flnX, fOutX, fOutXDsrFlow, fOutXCtsFlow устанавливаются В False;
fDtrControi у стан авл и вается в dtr_control_enable;
fRtsControi устанавливается в rts control enable;
口 для строк вида 9600,п, 8,1,х (заканчиваюшихся символом "х”):
finx, fOutx устанавливаются в True;
fOutXDsrFlow, fOutXCtsFlow устанавл И ВИ ЮТСЯ В False;
fDtrControi устанавливается в dtr_control_enable;
fRtsControi устанавливается в rts_control_enable;
для строк вида 9600, п, 8,1,р (заканчивающихся символом ”р)
flnX, fOutx устанавлИваются В False;
fOutXDsrFlow, fOutXCtsFlow устанавлИваются True;
fDtrControi устанавливается в dtr_control_handshake:
fRtsControi устанавливается в rts_control_handshake.
Важно отметить, что функция BuiidCommDCB только заполняет поля dcb указанными значениями. Это подготовитсяьный шаг к конфигурированию порта, но не само конфигурирование, которое выполняется функцией SetcommState. Поэтому вы можете вызвать BuildCommDCB для общего заполнения структуры dcb, затем изменить значения нужных полей и после этого вызывать функцию конфигурирования порта.
Дополнительные сведения
В Windows 95/98 из-за ошибки в реализации невозможно передать в качестве первого параметра константную строку — строку необходимо скопировать во временный буфер. При использовании константы возникает ошибка Access Violation.
Для заполнения структуры dcb можно использовать также функцию BuildCommDCBAndTimeouts. Еще одним простым способом заполнения dcb является вызов Getcommstate, возвращающий текущее значение полей dcb, изменение нужных полей и вызов SetcommState для установки новых значений.
Возвращаемое значение
Если функция BuildCommDCB выполнена успешно, возвращается ненулевое значение, иначе ——нулевое. Код ошибки можно получить с помощью вызова GetLastError.
Пример вызова
Листинг 21.7 показывает один из способов простого заполнения полей структуры DCB С ПОМОЩЬЮ вызова BuildCommDCB.
^ Листинг 21.7. Пример использования функции BuildCommDCB
Var
DCB : TDCB;
begin
FillMemory(@dcb, SizeOf(deb), 0);
deb.DCBlength:= SizeOf(deb);
if not BuildCommDCB(19600,n,8,11, deb) then begin
// DCB не создана.
Exit;
end;
// структура DCB готова к использованию end;
Функция BuildCommDCBAndTimeouts: создание структуры DCB
и тайм-аутов из строки
Функция Bui 1 dCommDCBAndTimeouts Позволяет ЗЯПОЛНЯТЬ структуры DCB И commtimeouts одновременно.
Формат заголовка BuiidCommDCBAndTimeouts на языке С имеет следующий вид:
BOOL Bui IdCommDCBAndTimeouts (
LPCTSTRIpDeff//строка
LPDCBIpDCB,/ / указатель на блок параметров
LPCOMMTIMEOUTS IpCominTimeOuts // структура тайм-аутов порта );
Формат заголовка Bui 1 dCommDCBAndTimeouts на языке Delphi имеет следующий вид:
function BuiIdCommDCBAndTimeouts (
IpDef: PChar;// строка
var IpDCB: TDCB;// указатель на блок параметров
var IpCominTimeouts: TCominTimeouts // структура тайм-аутов порта ):BOOL;
Функция аналогична BuidCommDCB, но, кроме заполнения dcb, позволяет заполнять ПОЛЯ структуры COMMTIMEOUTS ИЗ подстроки ТО = XXX:
□ при то = on функция включает использование тайм-аутов чтения и записи;
口 при то = off функция выключает использование тайм-аутов;
口 если параметр то = ххх не указан, функция не изменяет значении в структуре COMMTIMEOUTS.
Функции SetCommBreak
и ClearCommBrealc
управление выводом данных
Функция SetCommBreak замораживает передачу данных для указанного порта ДО тех пор, пока не будет вызвана функция ClearCommBreak.
Формат заголовков этих функций на языке С имеет следующий вид:
BOOL SetCommBreak(
HANDLE hFile // дескриптор порта, полученный из CreateFile
);
BOOL ClearCommBreak(
HANDLE hFile // дескриптор порта, полученный из CreateFile );
Формат заголовков на языке Delphi имеет вид:
function SetCommBreak(hFile: THandle): BOOL;
function ClearCommBreak(hFile: THandle): BOOL;
Последовательный канал передачи данных можно перевести в специальное состояние, называемое разрывом связи. При этом передача данных прекращается, а выходная линия переводится в состояние 0. Приемник, обнаружив, что за время, необходимое для передачи стартового бита, битов данных, бита четности и стоповых битов, приемная линия ни разу не перешла в состояние 1, так же фиксирует у себя состояние разрыва.
Следует заметить, что состояние разрыва линии устанавливается аппаратно. Поэтому нет другого способа возобновить прерванную с помощью SetCommBreak передачу данных, кроме вызова ClearCommBreak.
Единственный параметр этих функций задает дескриптор порта, полученный при вызове функции CreateFile.
Возвращаемое значение
Если выполнение прошло успешно, функция s е 七 сопшхв г еа к возвращает не- нулевое значение.
Функция ClearCommError. получение и сброс ошибок порта
Функция ClearCommError получает информацию об ошибках порта и сбрасывает флаги ошибок.
Формат заголовка ClearCommError на языке С имеет вид:
BOOL ClearCorranEr ror (
HANDLE hFile, // дескриптор порта LPDWORD IpError, // код ошибки LPCOMSTAT IpStat // состояние порта );
Формат заголовка на языке Delphi имеет вид:
function ClearCommError (
hFile:THandle; // дескриптор порта
var IpErrors: DWORD; // код ошибки IpStat:PComStat // состояние порта
):BOOL;
Входным параметром является дескриптор порта, полученный с помощью CreateFiie. Параметр ipError возвращает маску, состоящую из кодов ошибок, приведенных в табл. 21.1 (для полноты приведены все коды ошибок).
Таблица 21.1. Коды ошибок для Функции Cl еа rCommError
Маска
Значение
Величина
CE—BREAK
Обрыв линии
$010
CE—DNS
(Windows 95/98/МЕ) Порт не выбран
$800
CE—FRAME
Ошибка кадра
$008
CEJOE
Обнаружена ошибка ввода/вывода
$400
CEJ4ODE
Либо требуемый режим работы не поддерживается, либо параметр hFile задан неверно
$8000
СЕ_ООР
(Windows 95/98/МЕ, для параллельного порта). Закончилась бумага
$1000
CE—OVERRUN
Буфер полон. Следующий символ будет потерян
$002
СЕ_РТО
(Windows 95/98/МЕ, параллельный порт). Тайм-аут параллельного порта
$200
Таблица 21.1 (окончание)
Маска
Значение
Величина
CE—RXOVER
Входной буфер переполнен или символ был принят после получения символа конца EOF
$001
CE_RXPARITY
Ошибка паритета
$004
CE_TXFULL
Приложение пытается послать символ, но передатчик полон
$100
Поле cbinQue в структуре PComstat может использоваться для получения числа байтов во входной очереди порта. Эта величина необходима перед вызовом функции ReadFiie, т. к. перед чтением данных необходимо распределение памяти для буфера (листинг 21.2).
Возвращаемое значение
Если выполнение успешно, функция ciearCommError возвращает ненулевое значение.
Функция СапсеНо: прерывание асинхронных операций
Функция Canceiio прерывает вес эсинхронные операции чтения и зэписи для данного потока. Функция не влияет на операции в других потоках.
Формат заголовка на языке С имеет вид:
BOOL Cancello(
HANDLE hFile // дескриптор порта
);
Формат заголовка на языке Delphi имеет вид:
function Cancello(hFile: THandle): BOOL;
Параметр hFile задает дескриптор порта, полученный при открытии в функции CreateFile.
Возвращаемое значение
Ненулевое значение, если выполнение успешно.
Функция EscapeCommFunction: управление портом
Функция EscapeCommFunction позволяет передавать некоторые команды напрямую драйверу.
Формат заголовка на языке С имеет вил:
BOOL EscapeCommFunction (
HANDLE hFile, // дескриптор порта
DWORD dwFunc // номер функции
);
Формат заголовка на языке Delphi имеет вид:
function EscapeCommFunction(hFile: THandle; dwFunc: DWORD): BOOL;
Функция, указанная в параметре dwFunc, выполняется для порта с дескриптором hFiie. Указание на выполнение функции передается напрямую драйверу порта. Допустимые значения параметра приведены в табл. 21.2.
Таблица 21.2. Коды функции EscapeCommFunction
Функция
Описание
Значение
CLRDTR
Сбрасывает сигнал DTR
6
CLRRTS
Сбрасывает сигнал RTS
4
SETDTR
Устанавливает сигнал DTR
5
SETRTS
Устанавливает сигнал RTS
3
SETXOFF
Указывает передатчику выполнить операцию, имитирующую прием символа xoff
1
SETXON
Указывает передатчику выполнить операцию, имитирующую прием символа XON
2
SETBREAK
Замораживает передачу до выполнения функции ClearCommBreak ИЛИ функции EscapeCommFunction с параметром CLRBREAK. Эта функция не сбрасывает данные, которые еще не были переданы
8
CLRBREAK
Восстанавливает передачу данных, выполняя действия, аналогичные вызову ClearCommBreak
9
RESETDEV
Сброс устройства, если это возможно
7
GETCOMBASE
Получить базовый адрес порта (недокументированно). Только в Windows 95/98/МЕ
10
Возвращаемое значение
При успешном выполнении функция EscapeCommFunction возвращает ненулевое значение.
Функции GetCommMask
и SetCommMask: маска вызова событий
Функция GetCommMask позволяет получить флаги событий, а функция SetConmMask — задать эти флаги. Флаги задают типы событий, которые будут отслеживаться драйвером порта. Ожидание выбранных событий производится С ПОМОЩЬЮ фуНКЦИИ WaitCoininEvent.
Формат заголовков этих функций на языке С имеет вид:
BOOL GetCommMask( HANDLE hFile, LPDWORD IpEvtMask
);
BOOL SetCoiranMask ( HANDLE hFile, DWORDIpEvtMa sk
// дескриптор порта
// маска событий
// дескриптор порта
// маска событий
);
Delphi имеет вид:Delphi имеет вид:Формат заголовков на языке
Delphi имеет вид:
Delphi имеет вид:
function GetCoiranMas k (hFi 1 e:function GetCoiranMas k (hFi 1 e:function SetCorranMask(hFile:function SetCorranMask(hFile:THandle; var IpEvtMask: DWORD): BOOL;
function GetCoiranMas k (hFi 1 e:
function GetCoiranMas k (hFi 1 e:
function SetCorranMask(hFile:
function SetCorranMask(hFile:
THandle; dwEvtMask: DWORD): BOOL;
Маска событий состоит из флагов, приведенных в табл. 21.3.
Таблица 21.3. Маски событий для GetCommMa sk/SetCominMask
Значение
Описание
EV_BREAK
Прерывание ввода
EV_CTS
Изменение состояния линии CTS
EVJDSR
Изменение состояния линии DSR
EV_ERR
Одна из ошибок: ce_frame, ce_overrun или ce_rxparity
EV_RING
Сигнал RI
EV_RLSD
Изменение состояния RLSD
EV_RXCHAR
Принят символ
Таблица 21.3 (окончание)
ЗначениеОписание
EV_RXFLAGПолучен специальный символ, описанный в блоке DCB
ev txempty Буфер посылки опустошен
Возвращаемое значение
Не ноль, если выполнение успешно.
Пример вызова
См. пример в разд. 21.14.
Функция CreateEvent создание объекта ожидания
Функция CreateEvent создает или открывает именованный пли безымянный объект-событие.
Формат заголовка на языке С имеет вид:
HANDLE CreateEvent (
LP_SECURITY_ATTRIBUTES LpEventSA, // атрибуты защиты
BOOL bManualReset, // тип управления
BOOL blnitialState, // начальное состояние
LPCTSTR IpName//имя объекта
);
Формат заголовка на языке Delphi имеет вид:
function CreateEvent (
IpEventAttributes: PSecurityAttributes; // атрибуты защиты
bManuaiReset,// тип управления
blnitialState: BOOL;// начальное состояние
IpName: PChar// имя объекта
):THandle;
Параметр ipEventSA задает атрибуты защиты. Если этот параметр null, берутся параметры родительского процесса.
Параметр bManuaiReset задает режим управления событием. Если этот параметр True, то программист должен вызвать функцию ResetEvent для переключения события в несигнальное состояние. Если False, то объект автоматически переключается в несигнальное состояние после завершения ожидания.
Параметр binitiaistate задает начальное состояние объекта. Если True, то объект создается в сигнальном состоянии, иначе — в несигнальном.
Параметр ipName задает имя объекта. Имя ограничено длиной мах_ратн символов. Имя объекта регистрозависимо. Если вместо имени указано null, то создается безымянный объект. Имя может содержать любые символы, кроме символа ”\”.
Возвращаемое значение
Если выполнение успешно, функция CreateEvent возвращает ДССКрИПТОр созданного объекта. Если именованный объект уже существует, возвращается его дескриптор, а функция GetLastError ВОЗВращает ERROR_ALREADY_EXISTS. В случае ошибки возвращается null.
Пример вызова
См. пример в разд, 21.14.
Функция WaitCommEvent ожидание события СОМ-порта
Функция waitCommEvent ожидает наступления события, заданного маской функции SetCommMask.
Формат заголовка на языке С имеет вид:
BOOL WaitCommEvent (
HANDLEhFile,// дескриптор порта
LPDWORDIpEvtMask, // маска событий
LPOVERLAPPED IpOveгlapped // параметры );
Формат заголовка на языке Delphi имеет вид:
function WaitCorranEvent(
hFile:THandle;//дескриптор порта
var IpEvtMask:DWORD;//маска событий
IpOverlapped:POverlapped//параметры
):BOOL;
Если в процессе выполнения waitcommEvent маска событий будет изменена с помощью setcommMask, функция незамедлительно завершится, а указатель ipEvtMask будет сброшен в 0.
Функция waitcommEvent обнаруживает изменение состояния линий, но не сообщает об их текущем состоянии. Для получения текущего состояния линий CTS, DSR, RLSD и индикатора вызова можно использовать функцию GetCommModemStatus.
Параметр hFiie задает дескриптор порта, возвращаемый функцией CreateFile. Маска событий СОСТОИТ ИЗ тех же флагов, ЧТО И ДЛЯ SetCormnMask.
Указатель на overlapped требуется, если порт был открыт с флагом FILE_FLAG_OVERLAPPED. ЕСЛИ ПОрТ ОТКрЫТ С флЯГОМ FILE__FLAG_OVERLAPPED, а указатель ipove г lapped равен null, функция некорректно завершит работу. В случае, когда порт открыт с помощью file_flag_overlappeD, структура параметров должна содержать дескрипторы созданных с помощью CreateEvent объектов-событий.
Возвращаемое значение
Если выполнение успешно, функция WaitcommEvent возвращает ненулевое значение.
Дополнительные сведения
Версии Windows, основанные на Win32, не поддерживают событие ring для последовательных портов, задаваемое флагом ev_ring функции SetCoimnMask. Для обхода этой ситуации предлагается создать поток, обрабатывающий результат функции GetCommModemStatus:
While (true) do begin
GetCommModemStatus (hCommPort, 0dwModemSt atus);
if ((MS_R工NG_ON and dwModemStatus) <> 0) then
// обнаружен сигнал RING
End;
Пример вызова
Листинг 21.8 показывает использование функции waitcommEvent для организации потока чтения данных TReadThread. При инициализации устанавливается маска ожидания наличия данных ev_rxchar. Вызов waitcommEvent стартует ожидание ЭТОГО события, а функция WaitForSingleObject позволяет "разбудить" поток в нужный момент. Функция GetOveriappedResuit возвращает результат ожидания — маску произошелujих событий.
Перед чтением данных производится ВЫЗОВ функции ClearCommError, по- зволяющей узнать об ошибках порта, и самое главное ——получить число символов, доступных для чтения. Это позволяет корректно распределить память для буфера данных функции ReadFile.
Г™……F"E:*:■ *"…寸…- -.,-.,.
;Листинг 21.8. Пример использования WaitConunEvent
Procedure TReadThread.Execute;
Var Currentstate : TComStat;
AvaibleBytes, ErrCode, RealRead : Cardinal;
ReadOL : TOverLapped; {структура для асинхронного чтения}
Signaled, Mask : DWORD;
BytesTrans : DWORD; {не используется для WaitConimEvent}
bReadable : Boolean; {готовность к чтению данных}
Begin
With FOwner do begin
Try
{создание события для асинхронного чтения}
FillChar(ReadOL, SizeOf(ReadOL), 0);
ReadOL.hEvent:= CreateEvent(nil, True, True, nil);
{Маска событий, которые будет отслеживать читающий поток }
{Пока это только получение символа}
SetCorranMask(FHandle, EV_RXCH7XR);
While (not Terminated) and Connected do begin {пока порт открыт}
{ Ждем одного из событий }
WaitCorcmEvent(FHandle, Mask, 0ReadOL);
Signaled:= WaitForSingleObject(ReadOL.hEvent, INFINITE);
If (Signaled = WAIT_OBJECT_0) then begin
If GetOverlappedResult(FHandle, ReadOL, BytesTrans,
False) then begin
{после GetOverlappedResult в переменной mask, которая)
{передавалась в WaitCommEvent, появятся флаги произошедших}
{событий, либо 0 в случае ошибки}
If (Mask and EV RXCHAR) <> 0 then begin
{Получаем состояние порта (линий и модема) } ClearCommError(FHandle, ErгCode, @ Currentstate); { Число полученных, но еще не прочит энных байт) AvaibleBytes:= Currentstate.cblnQue;
{ Проверка числа доступных байт )
If FWaitFullBuffer then begin {ждать только полного буфера } bReadable:= AvaibleBytes >= FInBufSize;
End else begin
{ждать любого числа байт} bReadable:= AvaibleBytes > 0;
End;
If bReadable then begin
{Чистка буфера}
ZeroMemory(FInBuffer, FInBufSize);
If ReadFile(FHandle, FInBufferЛ, Min(FInBufSize, AvaibleBytes), RealRead, @ReadOL) then begin {сохраняем параметры вызова события} FEггCode:= ErrCode; FCount := RealRead;
{Вызываем событие OnReadByte. Для синхронизации c VCL} {надо вызвать метод Synchronize} Synchronize(DoReadPacket);
End;
End;
End;
End;
End;
End;
Finally
{закрытие дескриптора сигнального объекта) CloseHandle(ReadOL.hEvent);
{Сброс события и маски ожидания)
SetCommMask(FHandle, 0);
End;
End;
End;
Функции GetCommConfig и SetCommConfigi конфигурирование параметров порта
Функция GetCommConfig возвращает, а функция setcommconfig — устанавливает текущую конфигурацию коммуникационного устройства. Конфигурация передается с помощью структуры commconfig (см. разд. 20.1).
Формат заголовков этих функций на языке С имеет вид:
BOOL GetCommConfig( HANDLEhComiTiDev,LPCOMMCONFIG IpCC, LPDWORDIpdwSize);BOOL SetCommConfig( HANDLEhCommDev,LPCOMMCONFIG IpCC, DWORDdwSizeBOOL GetCommConfig( HANDLEhComiTiDev,LPCOMMCONFIG IpCC, LPDWORDIpdwSize);BOOL SetCommConfig( HANDLEhCommDev,LPCOMMCONFIG IpCC, DWORDdwSize// дескриптор порта// указатель на COMMCONFIG// возвращает размер IpCC// дескриптор порта// указатель на COMMCONFIG// передает размер 1рСС// дескриптор порта// указатель на COMMCONFIG// возвращает размер IpCC// дескриптор порта// указатель на COMMCONFIG// передает размер 1рСС
BOOL GetCommConfig( HANDLEhComiTiDev,
LPCOMMCONFIG IpCC, LPDWORDIpdwSize
);
BOOL SetCommConfig( HANDLEhCommDev,
LPCOMMCONFIG IpCC, DWORDdwSize
BOOL GetCommConfig( HANDLEhComiTiDev,
LPCOMMCONFIG IpCC, LPDWORDIpdwSize
);
BOOL SetCommConfig( HANDLEhCommDev,
LPCOMMCONFIG IpCC, DWORDdwSize
// дескриптор порта
// указатель на COMMCONFIG
// возвращает размер IpCC
// дескриптор порта
// указатель на COMMCONFIG
// передает размер 1рСС
// дескриптор порта
// указатель на COMMCONFIG
// возвращает размер IpCC
// дескриптор порта
// указатель на COMMCONFIG
// передает размер 1рСС
Формат заголовков на языке Delphi имеет вид:
function GetCommConfig( hCommDev:THandle;
var IpCC:TCommConfig;
var IpdwSize: DWORD
):BOOL;
// дескриптор порта
// указатель на COMMCONFIG
// возвращает размер IpCC
function SetCommConfig( hCommDev: THandle;
// дескриптор порта
const IpCC : TCommConfig;
// указатель на COMMCONFIG
dwSize: DWORD
):BOOL;
// возвращает размер IpCC
Первый параметр передает
дескриптор порта, полученный с помощью
функции createFiie. Второй параметр передает указатель на структуру параметров порта commconfig. Третий параметр либо возвращает размер структуры, либо передает его.
Возвращаемое значение
Возвращает ненулевое значение, если вызов успешен.
Пример вызова
Структура COMMCONGIG имеет переменную длину, поэтому для правильного распределения памяти надо вызвать ее дважды. Первый раз ——для определения необходимого размера памяти, а второй ——для получения данных (листинг 21.9).
■ Листинг 21,9, Пример вызова GefeComxnConf xg и SetConuuConfig
*;";♦““"."""*■:”.".工""".个““之J;.“.疏 Л "%*露"“—.一"""“...*).J;.*..*;;;"...;**:-i»».j....^>.....;..»S
Var
CoramHandle : THandle;
Buffer : PCcmmConfig;
Size: DWORD;
begin
{Открываем порт)
ComHandle:= CreateFile(...);
{Создаем временный буфер}
GetMem(Buffer, SizeOf (TCommConfig));
{Получаем необходимый размер буфера} Size:= 0;
GetCommConfig(ConimHandle, Buffer^, Size);
{Освободить временный буфер}
FreeMem(Buffer, SizeOf(TCommConfig));
{Создаем нужный буфер} GetMem(Buffer, Size); {Получаем данные} GetCommConfig (CoiumHandle, Buffer^, Size); {Устанавливаем скорость обмена} Buff егл.deb.BaudRate:= 1200;
{Задаем новую конфигурацию}
SetCommConfig(CoiumHandle, Buffer^, Size);
{Освобождаем буфер} FreeMem(Buffer, Size); {Закрываем порт} CloseHandle (CoiumHandle); end;
Функция CommConfigDialog: диалог конфигурирования порта
Функция ConimConfigDiaiog отображает диалог конфигурирования параметров порта (рис. 21.1).
Рис« 21.1. Диалог редактирования настроек порта(CommConfigDialog)
Функция CommConfigDialog не выполняет настройки порта. Она всего лишь позволяет пользователю изменить некоторые поля в блоке dcb, содержащемся в структуре commconfig. Кнопка Restore Default (Восстановить исходные значения) устанавливает настройки порта, заданные с помощью функции setDefauitCommConfig. Эти настройки можно прочитать с помощью вызова GetDefaultCommConf ig.
Формат заголовка на языке С имеет вид:
BOOL CommConfigDialog(
LPCTSTR IpszName, //LPCTSTR IpszName, //название портаназвание порта
LPCTSTR IpszName, //
LPCTSTR IpszName, //
название порта
название порта
HWND hWnd,HWND hWnd,// дескриптор окна, которому// будет принадлежать диалог
HWND hWnd,
HWND hWnd,
LPCCMMCONGIF 1рСС / / указатель на структуру COMMCONFIG );
Формат заголовка на языке Delphi имеет вид:
function ConimConfigDialog (
IpszName : PChar;
hWnd : HWND;
var IpCC : TCommConf ig
):BOOL;
Первый параметр задает имя порта (или имя устройства). В Windows 95/98/МЕ имя должно быть указано в короткой форме, т. е. СОМ1, но не \\.\comi. Второй параметр задает дескриптор окна, которому будет принадлежать диалог (может быть указан 0). Последний параметр передает указатель на структуру COMMCONFIG.
Возвращаемое значение
Возвращает ненулевое значение при успешном выполнении.
Дополнительные сведения
Для работы функции CommConfigDiaiog требуется специальный DLL-модуль, предоставляемый разработчиком коммуникационного устройства. Для COM-портов это модуль serialui.dll, для модемов — модуль modemui.dll. Вид диалога зависит от операционной системы и динамической библиотеки, предоставляемой производителем устройства.
Пример вызова
Var
comm : TCOMMCONFIG;
comm.dwSize:= sizeof (conim);
CommConfigDialog (*COM1 * f 0, conim);
Функция GetCommProperties: прочитать свойства порта
Функция GetCommPrope rt i e s возвращает структуру COMMPROP свойств порта.
Формат заголовка на языке С имеет вид:
BOOL GetConimProperties (
HANDLE hFile,// дескриптор порта
LPCOMMPROP IpCoramProp // указатель на структуру COMMPROP
);
Формат заголовка на языке Delphi имеет вид:
function GetCoramProperties(
hFile: THandle;
var IpCommProp : TCornmProp
):BOOL;
Первый параметр передает дескриптор порта, полученный с помощью createFiie. Второй параметр ——указатель на структуру commprop.
Возвращаемое значение
Ненулевое значение, если функция выполнена успешно.
Пример вызова
См. листинг 20.2.
Функции GetCommState и SetCommState: состояние порта
Функция GetCommState читает, а функция setcommstate устанавливает текущие настройки порта согласно данным, записанным в структуре dcb.
Формат заголовков этих функций на языке С имеет вид:
BOOL GetCommState(
HANDLE hFile, // дескриптор порта
LPDCB IpDCB // указатель на структуру DCB
);
BOOL SetCommState(
HANDLE hFile, // дескриптор порта
LPDCB IpDCB // указатель на структуру DCB );
Формат заголовков на языке Delphi имеет вид:
function GetCommState(hFile: THandle; var lpDCB: TDCB): BOOL;
function SetCommState(hFile: THandle; const IpDCB: TDCB): BOOL;
Первый параметр передает дескриптор порта, полученный с помощью CreateFiie. Второй параметр передает указатель на структуру dcb.
Возвращаемое значение
Возврашает ненулевое значение при успешном выполнении.
Пример вызова
Структура dcb имеет множество полей, заполнение которых вручную ——достаточно трудоемкая задача. Значительно проще считать текущее значение dcb с помощью вызова Getconimstate, изменить нужные поля структуры и вызвать SetConunState для установки новых параметров порта (листинг 21.10).
^Листинг21 ЛО. Пример вызова GetCommState И SetCommState
procedure TComPort.SetRxBuffer(Value: cardinal);
var DCB: TDCB;
begin
{Сохранить как новое значение свойства RxBuffer}
FRxBuffer := Value;
{Лимит XOFF равен 3/4}
FXOffLimit := Value div 4*3;
{Лимит XON равен 1/2}
FXOnLimit := Value div 2;
{Получить текущее значение полей DCB)
GetConimState (FComHandle, DCB);
{Установить новые значения}
DCB.XoffLim:= FXOffLimit;
DCB.XonLim := FXOnLimit;
{Отправить установки драйверу}
SetCommState(FComHandle, DCB); end;
Функции GetCommTimeouts и SetCommTimeouts: тайм-ауты порта
Функция GetConimTimeouts читает, а функция setConimTimeouts устанавливает текущие настройки тайм-аутов согласно данным, записанным в структуре COMMTIMEOUTS.
Формат заголовков этих функций на языке С имеет вид:
BOOL SetConimTiineouts (
LPCOMMTIMEOUTS IpCT // указатель на COMMTIMEOUTS
);
BOOL GetCornmT imeouts (
HANDLE hFile,// дескриптор порта
LPCOMMTIMEOUTS IpCT // указатель на COMMTIMEOUTS
);
Формат заголовка на языке Delphi имеет вид:
function SetCommTimeouts(
hE'ile: THandle;
const IpCoinmTimeouts: TcommTimeouts
):BOOL;
Первый параметр передает дескриптор порта, полученный с помощью CreateEnile. Второй параметр передает указатель на структуру commtimeouts.
Возвращаемое значение
Возвращает ненулевое значение при успешном выполнении.
Пример вызова
См. листинг 20.3.
Функция PurgeComm:
сброс буферов порта
Вызов функции PurgeComm удаляет все символы из входного и (или) выходного буферов. Также может прерывать незаконченные операции с портом.
Формат заголовка на языке С имеет вид:
BOOL PurgeConim (
HANDLE hFile, // дескриптор порта
DWORD dwFlag // флаги сбрасываемых буферов
);
Формат заголовка на языке Delphi имеет вид:
function PurgeCoinm(hFile: THandle; dwFlags: DWORD): BOOL;
Параметр hFile передает дескриптор порта, полученный с помощью
CreateEniie. Параметр dwFlag определяет маску операций согласно табл. 21.4.
Таблица 21.4. Маски операций для PurgeComm
Значение
Операция
PURGE—TXABORT
Завершает все асинхронные операции записи и завершается немедленно, даже если операции еще не завершились
PURGE_RXABORT
Завершает все асинхронные операции чтения и завершается немедленно, даже если операции еще не завершились
PURGE—TXCLEAR
Очищает выходной буфер (если ресурс имеет такой буфер)
PURGE_RXCLEAR
Очищает входной буфер (если ресурс имеет такой буфер)
Возвращаемое значение
При успешном выполнении возвращает ненулевое значение.
Пример вызова
Пример, показанный в листинге 21.11, может применяться для сброса входных и выходных буферов, например, перед изменением скорости обмена порта. Сброс буферов перед изменениями конфигурации гарантирует, что в выходной поток не попадут некорректные символы (и, аналогично, не будут приняты из входного буфера).
[Листинг 21X11^ Пример использования функции PurgeCornm
function TConimPort.FlushBuffers(blnBuf, bOutBuf: Boolean): Boolean;
var dwAction: DWORD;
begin
Result:= False;
// нет соединения — ошибка
if not Connected then Exit;
// маска операции
dwAction:= 0;
/ / Сброс выходного буфера
if bOutBuf then
dwAction:= dwAction or PURGE_TXABORT or PURGE_TXCLEAR;
// Сброс входного буфера
if blnBuf then
dwAction:= dwAction or PURGE_RXABORT or PURGE_RXCLEAR;
// Выполнить сброс
Result := PurgeComm (E'Handle, dwAction); end;
Функция SetupComm: конфигурирование размеров буферов
Функция SetupComm задает рекомендуемые размеры внутренних буферов - размеры в байтах очередей приема и передачи. Важно отметить, что это только рекомендуемые размеры. В общем случае система сама в состоянии определить требуемый размер очередей. Внутренние очереди драйвера позволяют избежать потери данных, если программа не успевает их считывать, и пауз в работе программы, если программа передает данные слишком быстро. Размер очереди выбирается немного большим максимальной длины сообщения. Указанные размеры очередей будут приняты драйвером к сведению. Но он оставляет за собой право внести коррективы или вообще отвергнуть устанавливаемое значение. В последнем случае функция завершится с ошибкой.
Формат заголовка на языке С имеет вид:
BOOL SetupCoinm (
// дескриптор порта
HANDLE hFile,
// рекомендуемый размер внутреннего входного буфера в байтах DWORD dwInQueue,
// рекомендуемый размер внутреннего выходного буфера в байтах DWORD dwOutQueue
);
Формат заголовка на языке Delphi имеет вид:
function SetupCoinm (
// дескриптор порта hFile: THandle;
// рекомендуемый размер внутреннего входного буфера в байтах dwInQueue,
// рекомендуемый размер внутреннего выходного буфера в байтах dwOutQueue: DWORD
):BOOL;;
Параметр hFile передает дескриптор порта, полученный с помощью CreateFile, dwinQueue передает рекомендуемый размер входного буфера, а dwOutQueue — выходного буфера.
В Windows NT/2000/XP размеры буферов должны быть четными числами. При попытке задать нечетные числа функция вернет error_invalid_data.
Возвращаемое значение
При успешном выполнении возвращает ненулевое значение.
Функции GetDefaultCommConfig и SetDefaultCommConfig: настройки порта по умолчанию
Функция GetDefaultConimConfig Читает, a SetDefaultCommConfig устанавливает настройки порта по умолчанию. Эти настройки будут приняты при нажатии кнопки Restore Defaults (Восстановить исходные значения) в диалоговом окне, вызываемом функцией commConfigDiaiog (см. разд. 21.16).
Формат заголовков этих функций на языке С имеет вид:
BOOL SetDefaultConimConfig (
LPCTSTR IpszName, // строковое имя порта
LPCOMMCONFIG IpCC, // указатель на COMMCONFIG
DWORDdwSize / / размер структуры IpCC
);
BOOL GetDefaultConimConfig (
LPCTSTR IpszName, // строковое имя порта
LPCOMMCONFIG IpCC, // указатель на COMMCONFIG
LPDWORD IpdwSize // размер структуры IpCC );
Формат заголовков на языке Delphi имеет вид:
function SetDefaultConimConfig (
IpszName: PChar;// строковое имя порта
IpCC: PConimConfig; // указатель на COMMCONFIG
dwSize : DWORD// размер структуры IpCC
):BOOL;
function GetDefaultConimConfig (
IpszName : PChar;/ / строковое 14мя порта
var IpCC : TCommConfig; // указатель на COMMCONFIG var lpdwSize: DWORD// размер структуры IpCC
):BOOL;
Первый параметр передает имя порта в строковом виде, как для CreateFiie. Второй параметр передает указатель на структуру параметров commconfig, а третий — передает (или принимает) размер структуры commconfig.
Возвращаемое значение
При успешном выполнении возвращает ненулевое значение.
Функция TransmitCommChar передача специальных символов
Иногда требуется срочная передача символа, имеющего специальное значение, а в очереди передатчика еще есть данные. Функция TransmitCommChar служит для передачи внеочередных символов вне зависимости от наличия данных в очереди передатчика.
При обнаружении символа, переданного этой функцией, выставляется флаг fTxim В Структуре CONSTAT.
Эта функция удобна для посылки символа прерывания обмена (например, <Ctrl>+<C>).
Специальный символ может быть передан только один раз — если символ, переданный с помощью этой функции, еще не отправлен в линию, то повторный вызов функции вернет ошибку.
Формат заголовка на языке С имеет вид:
BOOL TransmitCommCha г(
HANDLE hFile, // дескриптор порта
char cChar // передаваемый символ );
Формат заголовка на языке Delphi имеет вид:
function TransmitCommChar(hFile: THandle; cChar: CHAR): BOOL;
Первый параметр передает дескриптор порта, полученный с помощью CreateFiie. Второй параметр передает символ.
Возвращаемое значение
При успешном выполнении возвращает ненулевое значение.
Функция GetCommModemStatus: статус модема
Функция GetCominModemStatus возвращает состояние сигналов модема.
Формат заголовка на языке С имеет вид:
BOOL GetCoramModemStatus (
HANDLE hFile,// дескриптор порта
LPDWORD IpModemStatus // статус модема
);
Формат заголовка на языке Delphi имеет вид:
function GetConimModemStatus (
hFile: THandle;// дескриптор порта
var lpModemStat: DWORD / / статус модема ):BOOL;
Первый параметр передает дескриптор порта, полученный с помошью CreateFile. Второй параметр возвращает статус модема, состоящий из масок, приведенных в табл. 21.5.
Таблица 21.5. Маски состояния модема для GetCommModemSta tus
Маска
ОписаниеЗначение
MS_CTS_ON MS_DSR_ON MS_R 工 NG—ON MS_RLSD_ON
Сигнал CTS установлен$0010
Сигнал DSR установлен$0020
Сигнал вызова установлен$0040
Сигнал RLSD установлен$0080
Возвращаемое значение
При успешном выполнении возвращает ненулевое значение.
Пример вызова
Код, показанный в листинге 21.12, представляет собой функцию, возвращающую состояние всей линий модема. Вместо маски возвращается обычный для Delphi набор состояний.
;宏部::::二華 ……箕,…丁,?..二,,学*h
[ Листинг 21.12. Пример вызова GetComxnModemStatus
type
{Описание состояний линии}
TLineStatus = (IsCTS, IsDSR, IsRING, IsCD);
{Набор состояний линии}
TLineStatusSet = set of TLineStatus;
{Получить состояние линии} function TCornmPort.GetLineStatus: TLineStatusSet; var Status : DWORD;
begin
Result:=[];
{нет соединения}
if not Connected then exit;
{ получаем состояние линий модема }
if not GetCommModemStatus(FHandle, Status) then exit;
{разбираем полученную маску состояний}
if Status and MS_CTS_ON
<> 0
then
Result:
二 Result
+
[IsCTS
];
if Status and MS_DSR_ON
<> 0
then
Result:
:=Result
+
[IsDSR
];
if Status and MS_R工NG_ON
<> 0
then
Result:
=Result
+
[IsRING];
if Status and MS_RLSD_ON
<> 0
then
Result:
:=Result
+
[IsCD
];
end;
Функция WaitForSingleObject ожидание сигнального состояния объекта
Функция WaitForSingleObject ожидает сигнального состояния объекта синхронизации. Функция завершается в двух случаях:
口 объект перешел в сигнальное состояние;
П по завершении тайм-аута ожидания.
Формат заголовка на языке С имеет вид:
DWORD WaitForSingleObject(
HANDLE hHandle,// дескриптор объекта
DWORDdwMi11isecorids // пауза ожидания сигнального состояния
);
Формат заголовка на языке Delphi имеет вид:
function WaitForSingleObject (
hHandle: THandle; // дескриптор объекта
dwMilliseconds: DWORD // пауза ожидания сигнального состояния
):DWORD;
Первый параметр передает дескриптор объекта (порта). Пауза ожидания dwMiiiiseconds задается в миллисекундах. Специальная константа infinite задает неограниченное время ожидания.
При необходимости ожидания сигнального состояния одного из нескольких объектов или всех объектов одновременно следует воспользоваться функцией WaitForMultipleObj ects, а Не объединять WaitForSingleObject С ПОМОЩЬЮ OR ИЛИ AND,
Возвращаемое значение
При успешном выполнении функция waitForSingieObject возвращает значение, указывающее на состояние объекта. При ошибке возвращает WAiT_FA工led. В табл. 21.6 приводятся возможные результаты функции.
Таблица21.6. Результаты функции WaitForSingieObject
Код
Описание
WAIT_ABANDONED
Используется для мьютексов. Возвращается в случае, когда объект не был освобожден, хотя поток, его создавший, уже завершен. В нашей книге мы не используем мьютексы
WAIT_OBJECT_0
Объект перешел в сигнальное состояние
WAIT_TIMEOUT
Тайм-аут завершен, а сигнальное состояние не достигнуто. Естественно, в случае infinite такой результат невозможен
WAIT_FAILED
Ошибка вызова WaitForSingieObject
Функция WaitForMultipleObjects: ожидание сигнального состояния объектов
При необходимости ожидания сигнального состояния одного из нескольких объектов одновременно, следует воспользоваться функцией WaitForMultipleObjects, а не объединять WaitForSingieObject С ПОМОЩЬЮ
оператора or. Эта функция ожидает сигнального состояния одного или всех вместе объектов синхронизации. Функция завершается в двух случаях:
П один или все объекты перешли в сигнальное состояние;
口 по завершении тайм-аута ожидания.
Формат заголовка на языке С имеет вид:
DWORD WaitForMultipleObjects(
DWORD
nCount,
// число откидаемых объектов
const
HANDLE * IpHandles,
// массив дескрипторов объектов
BOOL
bWaitAll
/ / флаг **ожидать все объекты"
DWORD
dwMilliseconds
// пауза ожидания сигнального состояния
);
Формат заголовка на языке Delphi имеет вид:
function WaitForMultipleObjects(
nCount: DWORD;// число ожидаемых объектов
IpHandles : PWOHandleArray;// массив дескрипторов объектов
bWaitAll: BOOL;// флаг nожидать все объекты11
dwMiHiseconds: DWORD// пауза ожидания сигнального состояния
):DWORD;
Первый параметр передает число объектов синхронизации, дескрипторы которых передаются во втором параметре. Если третий параметр True, функция будет ожидать сигнального состояния всех объектов, иначе ——хотя бы одного из них. Четвертый параметр задает паузу ожидания так же, как в функции waitForSingieObject. Пауза ожидания задается в миллисекундам. Специальная константа infinite задает неограниченное время ожидания.
Возвращаемое значение
При успешном выполнении функция WaitForMultipleObjects возвращает значение, указывающее на состояние объектов. При ошибке возвращает wait_failed. В табл. 21.7 приводятся возможные результаты функции.
Таблица 21.7. Результаты функции Wai tForMul tipi eObjects
Код
Описание
От
WAIT_ABANDONED_0
ДО
(WAIT_ABANDONED_0+nCount-l)
Если bWaitAll = True, возвращаемое значение показывает, что один из объектов — неосвобожденный мьютекс. Если bWaitAll = False, то результат функции минус константа waiT_abanDONED_0 будет равен индексу неосвобожденного мьютекса в массиве IpHandles
Таблица 21.7 (окончание)
Код
Описание
От
WAIT_OBJECT_0
ДО
(WAIT_OBJECT_0+nCount-l)
Если bWaitAll = True, то все объекты перешли в сигнальное состояние. Если False, то результат функции минус константа WAIT_OBJECT_0 будет равен индексу объекта в массиве IpHandles, первым перешедшего в сигнальное состояние
WAIT_TIMEOUT
Тайм-аут завершен, а сигнальное состояние ни одного объекта не достигнуто. Естественно, в случае INFINITE такой результат невозможен
WAIT_FAILED
Ошибка вызова WaitForSingleObject
Функция GetOverlappedResult результат асинхронной операции
Функция GetOverlappedResult возвращает результат асинхронной операции с файлом или коммуникационным устройством.
Формат заголовка на языке С имеет вид:
BOOL GetOverlappedResult(
HANDLEhHandle,//дескрипторов объекта(файла или порта)
LPOVERLAPPED IpOveгlapped,// структура асинхронного вызова LPDWORDIpNBT,//число переданных илипрочитанных байт
BOOLbWait//флаг ожидания
);
Формат заголовка на языке Delphi имеет вид:
function GetOverlappedResult(
hFile: THandle; // дескрипторов объекта (файла или порта)
const IpOveг1apped: TOver1apped; // структура асинхронного вызова var IpNBT: DWORD; // число переданных или прочитанных байт
bWait: BOOL// флаг ожидания
):BOOL;
Первый параметр передает дескриптор файла или коммуникационного устройства. Перед вызовом этот дескриптор должен быть связан с асинхронной операцией, используя функции ReadFile, WriteFile, DeviceloControl ИЛИ WaitCommEvent.
Второй параметр передает структуру overlapped, которая использовалась при начале асинхронной операции.
Третий параметр возвращает число байтов, реально прочитанных или записанных в результате операции. Этот параметр не используется для коммуникационных портов.
Если параметр bwait равен True, то функция не завершится, пока операция не будет завершена. Если False и операция продолжается, то функция вернет False, а ВЫЗОВ GetLastError вернет ERROR—工O_INCOMPETE.
В Windows 95/98/МЕ, если параметр bwait равен True, то поле hEvent в структуре overlapped не может быть NULL.
Возвращаемое значение
При успешном выполнении возвращает ненулевое значение.
Функция DeviceloControh прямое управление драйвером
Функция DeviceioControi посылает команду, задаваемую кодом dwioCode, напрямую драйверу устройства с дескриптором hDevice, указывая ему выполнить определенные действия.
Для коммуникационных устройств дескриптор можно получить с помощью вызова createFiie с обязательным использованием флага асинхронных операций file_flag_overlapped.
Формат заголовка на языке С имеет вид:
BOOL DeviceioControi(
HANDLE
hDevice,
// дескриптор устройства
DWORD
dwIoCode,
// код выполняемой функции
LPVOID
IpInBuffer.
// указатель на входные данные
DWORD
dwInBufSize,
// размер входного буфера
LPVOID
IpOutBuffer,
// указатель на выходной буфер
DWORD
nOutBufSize,
// размер выходного буфера
LPDWORD
IpBytesRetn,
// требуемый размер выходного буфера
LPOVERLAPPED
IpOverlapped
// структура асинхронного вызова
Формат заголовка на языке Delphi имеет вид:
function DeviceioControi(
hDevicehDevice: THandle; // дескриптор устройства
hDevice
hDevice
dwIoControlCode : DWORD;dwIoControlCode : DWORD;// код выполняемой функции// код выполняемой функцииIpInBufferIpInBuffer:Pointer;:Pointer;nlnBufferSize : DWORD;nlnBufferSize : DWORD;IpOutBufferIpOutBuffer:Pointer;:Pointer;nOutBufferSizenOutBufferSizeDWORD;DWORD;var IpBytesRetn : DWORD;IpOverlapped: Poverlappedvar IpBytesRetn : DWORD;IpOverlapped: Poverlapped// указатель на входные данные// размер входного буфера// указатель на выходной буфер// размер выходного буфера// требуемый размер выходного буфера// структура асинхронного вызова// указатель на входные данные// размер входного буфера// указатель на выходной буфер// размер выходного буфера// требуемый размер выходного буфера// структура асинхронного вызова
dwIoControlCode : DWORD;
dwIoControlCode : DWORD;
// код выполняемой функции
// код выполняемой функции
IpInBuffer
IpInBuffer
:Pointer;
:Pointer;
nlnBufferSize : DWORD;
nlnBufferSize : DWORD;
IpOutBuffer
IpOutBuffer
:Pointer;
:Pointer;
nOutBufferSize
nOutBufferSize
DWORD;
DWORD;
var IpBytesRetn : DWORD;
IpOverlapped: Poverlapped
var IpBytesRetn : DWORD;
IpOverlapped: Poverlapped
// указатель на входные данные
// размер входного буфера
// указатель на выходной буфер
// размер выходного буфера
// требуемый размер выходного буфера
// структура асинхронного вызова
// указатель на входные данные
// размер входного буфера
// указатель на выходной буфер
// размер выходного буфера
// требуемый размер выходного буфера
// структура асинхронного вызова
):BOOL;
Параметр IpInBuffer передает входные данные, если они необходимы для выполнения операции. Может быть передано null, если лля выполнения операции не требуется дополнительная информация. Размер передаваемых входных данных задается параметром dwinBufSize.
Если функция, задаваемая кодом dwioCode, должна возвращать данные, то для этих данных передается указатель на буфер IpOutBuffer и его размер nOutBufSize. ЕСЛИ фуНКЦИЯ Не возвращает данные, IpOutBuffer должно быть установлено В NULL, a nOutBufSize в 0.
Последний параметр возвращает реальный размер данных, сохраненных в ipoutBufSize. Если буфер слишком маленький, функция завершится с ошибкой, а вызов GetLastError вернет error_insufficient_buffer. Значение IpBytesRetn в этом случае будет равно 0. Некоторые драйверы, если буфер мал для принятия полного пакета данных, возвращают только часть данных. В этом случае GetLastError возвращает значение error_more_data, a IpBytesRetn равно числу полученных байтов. Приложение должно снова вызвать DeviceioControi с теми же параметрами, указав новый стартовый адрес.
Если ipoverlapped равно null, то ipByteRetn не должно быть null. Даже если функция не возвращает данные и IpOutBuffer равно null, вызов DeviceioControi будет использовать IpBytesRetn. Однако после таких операций значение IpBytesRetn бессмысленно.
Если IpOverlapped Не равно NULL, ТО IpByteRetn МОЖеТ быть NULL. ЕСЛИ этот параметр не null и операция возвращает данные, то IpBytesRetn не имеет смысла, пока вызванная асинхронная операция не завершится. Для получения результата и числа возвращенных байтов используется вызов GetOverlappedResult. ЕСЛИ hDevice СВЯЗЗН С ПОрТОМ ВВОДа/выВОДЗ, то получить число возвращенных байтов можно с помощью функции GetQueuedCompletionStatus.
Если IpOverlapped не null, то она должна содержать указатель на структуру overlapped. Если hDevice был открыт без использования флага FiLE—FLAG—overlapped, то этот параметр будет игнорироваться. Иначе one- рация будет расцениваться как асинхронная. Структура IpOverlapped в этом
случае должна содержать правильный дескриптор объекта события, иначе функция завершится с непредсказуемой ошибкой.
Дия асинхронных операций функция DeviceloControl завершается сразу же, а сигнальный объект-событие будет сигнализировать о завершении операции. В синхронном режиме функция не завершится, пока не завершится операция, или по ошибке.
Важно отметить, что в Windows 95/98/МЕ эту функцию можно применять только к дескрипторам виртуальных драйверов. Например, для открытия системного VxD-драйвера надо передавать в функцию CreateFile имя \\.\vwin32.
Коды функций зависят от конкретного устройства. Для последовательных портов эта функция может применяться, только начиная с Windows 2000. Коды функций для поеледовательных портов приводятся в гл. 22
Возвращаемое значение
При успешном выполнении функция DeviceioControl возвращает ненулевое значение. В случае ошибки вернет 0, а вызов GetLastError вернет код ошибки.
Функция EnumPorts: перечисление портов
Функция EnumPorts производит перебор всех портов системы. Если pName равно null, производится перебор локальных портов, иначе — портов указанного сервера (для портов принтера).
Формат заголовка на языке С имеет вид:
BOOL EnumPorts(
// имя сервера (для портов принтера)
LPTSTR
pName,
DWORD
Level,
// тип получаемой информации
LPBYTES
pPorts,
// буфер информации 。 порте
DWORD
cbBuf,
// размер информации в pPorts
LPDWORD
pcbNeeded,
// число записанных или требуемых байт
LPDWORD
pcReturned
// число перечисленных портов
Формат заголовка на языке Delphi имеет вид:
function EnumPorts(
pName : PChar;// имя сервера (для портов принтера)
Level : DWORD;// тип получаемой информации
pPorts: Pointer;cbBuf : DWORD;var pcbNeeded, pcRe tu me d : DWORD):BOOL;pPorts: Pointer;cbBuf : DWORD;var pcbNeeded, pcRe tu me d : DWORD):BOOL;// // // //// // // //буфер информации о порте
pPorts: Pointer;
cbBuf : DWORD;
var pcbNeeded, pcRe tu me d : DWORD
):BOOL;
pPorts: Pointer;
cbBuf : DWORD;
var pcbNeeded, pcRe tu me d : DWORD
):BOOL;
// // // //
// // // //
размер информации в pPorts
число записанных или требуемых байт
число перечисленных портов
Параметр Level может принимать два значения: 1 или 2. Если он равен 1, то функция возвращает в буфер pPorts набор структур port_info_i, иначе — port_工NFO 2. Эта структуры описываются следующим образом: P0RT_ 工 NFO_1 = record pName: PWideChar;
end;
PORT_ 工 NFO_2 = record pPortName : PWideChar; pMonitorName: PWideChar; pDescription: PWideChar; fPortType: DWORD;
Reserved: DWORD;
end;
Параметр cbBuf описывает размер блока памяти pPorts. Как принято в Windows API, параметр pcbNeeded возвращает размер буфера, необходимого для выполнения функции. Если cbBuf (и, соответственно, сам буфер pPorts) слишком маленький, функция завершится с ошибкой, а GetLastError вернет КОД ERROR_INSUFFICIENT_BUFFER. В ЭТОМ Случае pcbNeeded будет содержать нужный размер буфера. Если же cbBuf будет больше или равен необходимому размеру, то pcbNeeded будет содержать число реально записанных в буфере данных.
Параметр pcReturned возвращает число структур, записанных в буфер pPorts.
Для невиртуальных портов их имена возвращаются со знаком ”二 например, "С0М1:”,а для виртуальных — без него, например, "FILE*1.
Дополнительные сведения
Для использования функции EnmuPorts в Delphi необходимо подключить модуль WinSpool.
Возвращаемое значение
При успешном выполнении функция EnumPorts возвращает ненулевое значение. В случае ошибки вернет 0, а ВЫЗОВ GetLastError вернет код ошибки.
Пример вызова
Хотя в MSDN функция EnumPorts описывается для портов принтера, она также возвращает и коммуникационные порты. Отличить одни порты от других можно, сравнив начало имени порта со строкой СОМ (листинг 21.13).
..............-工炉呼二""芝禁遇"■二 :懸蒸'"•贅飞.*• “£ ",尹二 .•■ "“,"";““." j Листинг 21ЛЗ. Пример вызова Функции EnumComPorts
{ перечисление имен всех доступных коммуникационных портов}
class procedure TComPort.EnumComPorts(Ports: TStrings);
var
BytesNeeded, Returned, I: DWORD;
Success: Boolean;
PortsPtr: Pointer;
InfoPtr: PPortlnfol;
TenpStr: string;
begin
{Запрос размера нужного буфера}
Success := EnumPorts(nil, 1, nil, 0, BytesNeeded, Returned);
If (not Success) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then begin
{Отводим нужный блок памяти}
GetMem(PortsPtr, BytesNeeded);
Try
{Получаем список имен портов}
Success := EnumPorts(nil, 1, PorrsPtr, BytesNeeded, BytesNeeded, Returned);
{Переписываем имена в StringList, отсеивая не-ССМ-порты}
For I := 0 to Returned - 1 do begin
InfoPtr := PPortInfol(DWORD(PortsPtr) + I * SizeOf(TPortlnfol));
TempStr := InfoPtrл.pName;
If Copy(TempStr, 1, 3) = *CCM* then
Ports. Add (TempStr);
End;
Finally
{Освобождаем буфер}
FreeMem(PortsPtr);
End;
End;
end;
Функция QueryDosDevice: получение имени устройства по его DOS-имени
Функция QueryDosDevice возвращает информацию о DOS-имени устройства. Функция может получить текущее значение NT-имени для данного DOS-имени или вернуть список всех DOS-имен системы.
Формат заголовка на языке С имеет вид:
DWORD QueryDosDevice(
LPCTSTR pName, // DOS-имя устройства
LPTSTR pResult,// буфер для результата
DWORD dwMax, // размер буфера pResult );
Формат заголовка на языке Delphi имеет вид:
function QueryDosDevice(
IpDeviceName : PChar; // DOS-имя устройства
IpTargetPath : PChar; // буфер для результата
ucchMax : DWORD // размер буфера pResult
):DWORD;
Первый параметр передает DOS-имя устройства, например, "СОМ Г' или WC:". В этом случае в буфер pResult будет записано внутреннее имя устройства. Если же pName нулевой, то в буфер будут записаны все DOS-имена, зарегистрированные в системе. Каждое имя —— строка, завершающаяся нуль- символ ом. Признак завершения таблицы имен —— двойной нуль-символ (т. е. пустая строка).
Второй параметр передает буфер для сохранения результата. Размер буфера передается в параметре dwMax. Если размер буфера мал, то результат выполнения зависит от версии операционной системы (см. ниже).
Возвращаемое значение
При успешном выполнении функция QueryDosDevice возвращает ЧИСЛО символов, сохраненных в буфере. В случае ошибки вернет 0, а вызов GetLastError вернет код ошибки.
Если буфер имеет недостаточный размер, функция вернет 0, а вызов GetLastError ВбрНеТ КОД ERROR_INSUFFICIENT_BUFFER. В Windows NT/2000, если буфер мал, функция запишет в буфер столько данных, на сколько хватит буфера.
Пример вызова
Листинг 21.14 показывает получение списка всех имен в системе с помощью ВЫЗОВа QueryDosDevice.
;Листинг 21.14. Пример вызова функции QueryDosDevice
{Получение всех имен устройства}
procedure TForml.btnGetListClick(Sender: TObject);
var BufSize : Cardinal; P, PName : Pointer; SName : String; begin
{Очищаем предыдущий список)
IbNameList.Items.Clear;
{Размер буфера}
BufSize:= 10240;
{Распределяем память для буфера}
GetMem(P, BufSize);
{Запрашиваем список имен)
If QueryDosDevice(nil, Р, BufSize) О 0 then begin
{Цикл no всем именам...}
PName:= Р;
While (True) do begin
SName:= StrPas(PName);
If SName = '* then Break;
{Добавляем в список}
IbNameList. Items.Add (SName);
{Переход к следующему устройству}
{Сдвигаем указатель на следующую строку}
PName:= Pointer(Longlnt(PName) + Length(SName)+1);
End;
End;
{Освобождаем буфер}
FreeMem(P);
end;
Функция DefineDosDevice: операции с DOS-именем устройства
Функция DefineDosDevice определяет, переопределяет или удаляет DOS-имя из системы. Выполняемая операция зависит от параметра dwFiags. Его возможные значения приведены в табл. 21.8.
Формат заголовка на языке С имеет вид:
BOOL DefineDosDevice(
DWORD dwFiags, // код операции
LPCTSTR pName, / / DOS-имя устройства или его префикс
LPCTSTR pDevice, // NT-имя устройства
);
Формат заголовка на языке Delphi имеет вид:
function DefineDosDevice(
dwFiags : DWORD; // код операции
IpDeviceName,// DOS-имя устройства или его префикс
IpTargetPath : PChar // NT-имя устройства ):BOOL;
Первый параметр задает код операции (табл. 21.8). Второй параметр передает DOS-имя устройства, а третий — внутреннее имя устройства. Для выполнения этой функции требуются права адм инистратора системы
Возвращаемое значение
При успешном выполнении функция DefineDosDevice ВОЗВращает ненулевое значение. В случае ошибки функция вернет 0, а вызов GetLastError вернет код ошибки.
Пример вызова
Листинг 21.15 показывает добавление имени в систему.
Листинг 21.15. Пример вызова DefineDosDevice
{Добавить DOS-имя для NT-имени}
procedure TForml.btnAddNameC1iсk(Sender: TObject); begin
If not DefineDosDevice(DDD RAW TARGET PATH,
PChar(Dos Devi ceName.Text),
PChar(NtDeviceName.Text)) then
StatusBar.Panels[0].Text:= 'Ошибка добавления имени *;
end;
Таблица 21.8. Коды операций функции DefineDosDevice
Значение
Описание
DDD_RAW_TARGET_ PATH
При указании этого параметра функция не конвертирует значение pDevice в имя устройства, а берет указанное значение имени "как есть"
DDD_REMOVE_ DEFINITION-
Удаление указанного DOS-имени для выбранного устройства. Функция просматривает все имена, зарегистрирован- ные для устройства, и удаляет те, префиксы которых совпадают с указанной в pName строкой. Если существует несколько таких имен для устройства, функция удалит только первое из них.
Если параметр pDevice нулевой или указывает на строку нулевой длины, то функция удалит первое подходящее имя из списка имен системы
DDD_EXACT_MATCH_ ON_REMOVE~
Указывается вместе с ddd__REMOVE_definition для гарантирования, что функция не удалит лишнего. Функция будет удалять имя только при полном совпадении имени из списка имен и указанного в pName
Глава 22
Функции DiviceloControl для последовательных портов
Описание и параметры функции DeviceioControi приведены в гл. 2/, а в этой главе мы опишем функции коммуникационного драйвера, которые МОЖНО вызывать С ПОМОЩЬЮ DeviceioControi.
Еще раз отметим, ЧТО функции DeviceioControi ДЛЯ ПОСЛедОВЗТеЛЬНЫХ ПОр- тов можно использовать только начиная с Windows 2000.
Функции драйвера последовательного порта
Все приведенные функции описываются в контексте параметров DeviceioControi:
для вызова конкретной функции необходимо сформировать ее код с помощью функции Get_Ctl_Code (числовой_код__функции) ИЗ разд. 13.2;
указание относительно входных и выходных параметров функции нужно расценивать как указание на содержимое буферов ipinBuffer и IpOutBuffer функции DeviceioControi (см. разд. 21.28).
Последнее означает, что указание ^входные параметры отсутствуют** свидетельствует О ТОМ, ЧТО IpinBuffer = nil, dwInBufSize = О, НО при НЗЛИЧИИ выходных параметров функции передается значение dwOutBufSize.
Далее перечислим функции DeviceioControi для последовательных портов.
Функция ioctl_serial_set_baud_rate устанавливает скорость обмена последовательного порта. Входной буфер должен содержать указатель на переменную типа Cardinal, равную скорости обмена. Выходных параметров нет.
Листинг 22Л. Пример вызова Device I oControl (1)
Baud : Cardinal;
Baud:= 19200;
DeviceloCont rol(
FHandle, Get_Ct l_Code (1),
@Baud, SizeOf(Baud),
nil, 0, Skip, nil
);
В MSDN почему-то указывается, что скорость обмена задается константами ser工al_baud_xxx, однако простая проверка показывает, что скорость обмена задается константами cbr_xxx, которые используются в структуре dcb.
Аналог В Windows API — функция SetCommState.
口 Функция ioctl_serial_set_queue_size устанавливает размер внутреннего приемного буфера. Если требуемый размер буфера больше, чем текущий размер, то будет создан новый буфер, иначе размер буфера не изменится. Входной параметр ——структура tserial_queue_size, выходных параметров нет. Аналог в Windows API ——функция SetupComm.
口 Функция 工octL—Ser工al_set_l工ne_control устанавливает значение порта LCR (Line Control Register), который содержит характеристики обмена: число стоп-битов, число битов в байте и настройки контроля четности. Входной параметр — структура tser工al_l工ne_control, выходных параметров нет.
Поля этой структуры могут принимать значения:
Значения ПОЛЯ WordLength:
0 SER 工 AL_5_DATA = 5;
0 SER 工 AL_6_DATA = 6;
0 SER 工 AL_7_DATA = 7;
0 SER 工 AL_8_DATA = 8.
Значения поля stopBits:
0 STOP—В工Т_1= 0;
0 STOP—B工ts_i_5 = 1 (только для 5-битовых данных);
0 stop_bits_2 = 2 (не для 5-битовых данных).
Значения ПОЛЯ Parity:
О NO_PARITY = О;
О ODD_PARITY = 1;
О EVEN_PAR 工 TY = 2;
О MARK_PAR工TY = 3;
О SPACE一PARITY = 4.
Аналог В Windows API ——функция SetCommState.
口 Функция ioctl_serial_set_break_on устанавливает сигнал прерывания так же, как SetCoiranBreak. Входных и выходных параметров нет. Ту же операцию МОЖНО ВЫПОЛНИТЬ ВЫЗОВОМ EscapeCommFunction (SETBREAK).
Функция 工。ctl_ser工al_set_break_off сбрасывает сигнал прерывания так же, как ciearCommBreak. Входных и выходных параметров нет. Ту же операцию МОЖНО ВЫПОЛНИТЬ ВЫЗОВОМ EscapeConunFunction (CLRBREAK).
口 Функция 工OCTL_SER工al_工mmed工atE—Char требует передачу специального символа вне очереди, аналогично вызову тransmitCommChaг. Входной параметр ——указатель на переменную типа byte, содержащую код передаваемого символа. Выходных параметров нет.
Функция 工OCTL—SER工AL—SET—T工meouts устанавливает значение тайм-аутов порта. Входной параметр — указатель на структуру tserial_timeouts. Выходных параметров нет. Аналог в Windows API — функция SetCommTimeouts.
Функция ioctl_ser I al_ge т_т IME out s возвращает информацию о тайм-аутах порта. Входных параметров нет, выходной —— указатель на структуру tser 工 al_t 工 meouts. Аналог в Windows API — функция GetCommTimeouts.
口 Функция 工octL—Ser工AL_SET_DTR устанавливает сигнал DTR. Если драйвер сконфигурирован на автоматическое управление этим сигналом, функция не выполнится. Входных и выходных параметров нет. Аналогичные действия МОЖНО ВЫПОЛНИТЬ ВЫЗОВОМ EscapeCommFunction (SETDTR).
口 Функция 工octl_ser工al_clr_dtr сбрасывает сигнал DTR. Если драйвер сконфигурирован на автоматическое управление этим сигналом, функция не выполнится. Входных и выходных параметров нет. Аналогичные действия МОЖНО ВЫПОЛНИТЬ ВЫЗОВОМ EscapeCoiranFunction (CLRDTR).
О Функция 工OCTL SER工AL—RESET DEV工СЕ ПРОИЗВОДИТ рестарт ПОрТЗ. ВХОДНЫХ и выходных параметров нет. Аналогичные действия можно выполнить ВЫЗОВОМ EscapeCommFunction (RESETDEV).
Функция 工octl_ser工AL—SET RTS устанавливает сигнал RTS. Если драйвер сконфигурирован на автоматическое управление этим сигналом, функция
не выполнится. Входных и выходных параметров нет. Аналогичные действия МОЖНО ВЫПОЛНИТЬ ВЫЗОВОМ EscapeCommFunction (SETRTS).
Функция 工OCTL_SER工AL CLR—RTS сбрасывает сигнал RTS. Если драйвер сконфигурирован на автоматическое управление этим сигналом, функция не выполнится. Входных и выходных параметров нет. Аналогичные действия МОЖНО ВЫПОЛНИТЬ ВЫЗОВОМ EscapeCommFunction (CLRRTS).
Функция ioctl_serial_set_xoff эмулирует получение символа Xoff. Если приложение не использует автоматическое управление обменом с помощью xon/xoff, клиент должен сам подать сигнал хоп. Входных и выходных параметров нет. Аналогичные действия можно выполнить вызовом EscapeCommFunction(SETXOFF).
П Функция iocTL_SER工al_set_xon эмулирует получение символа Хоп. Если приложение не использует автоматическое управление обменом с помощью xon/xoff, клиент должен сам подать сигнал xoff. Аналогичные действия МОЖНО ВЫПОЛНИТЬ ВЫЗОВОМ EscapeCoirunFunction (SETXON).
口 Функция Ioctl_serial_get_wait_ma возвращает маску ожидания событий. Аналог функции GetcommMask. Входных параметров нет, выходной - указатель на переменную типа cardial, содержащую маску флагов событий (табл. 22.1).
Таблица 22.1. Маски ожидания для функции тосtl_see 1Д1_get_wait_mask
Флаг
Значение
Описание
SERIAL_EV_RXCHAR
$0001
Получен символ
SER工AL_EV_RXFLAG
$0002
Получен специальный символ, описанным в блоке DCB
SERIAL_EV_TXEMPTY
$0004
Буфер посылки опустошен
SERIAL_EV_CTS
$0008
Сигнал CTS изменил состояние
SERIAL_EV_DSR
$0010
Сигнал DSR изменил состояние
SER工AL_EV_RLSD
$0020
Сигнал RLSD изменил состояние
SERIAL_EV_BREAK
$0040
Получен сигнал BREAK
SER 工 AL_EV_ERR
$0080
Произошла ошибка приема
SERIAL_EV_RING
$0100
Обнаружен сигнал вызова
SER工AL_EV_PERR
$0200
Ошибка принтера
SER工AL_EV_RX80FULL
$0400
Приемный буфер заполнен более чем на 80 процентов
SER工AL_EV_EVENT1
$0800
Событие, определяемое драйвером
SERIAL_EV_EVENT2
$1000
Событие, определяемое драйвером
Следует отметить, что, хотя аналогом этой функции является функция GetCominMask, анЗЛОГОВ флаГОВ SERIAL_EV_PERR, SER 工 AL_EV_RX80FULL, SERIAL_EV_EVENT 1 И SER工AL_EV_EVENT2 ДЛЯ GetComraMask He существует.
口 Функция ioctl_serial_set_wait_mask устанавливает маску ожидания событий. Входной параметр ——указатель на переменную типа Cardinal, содержащую маску событий согласно табл. 22.1. Аналог в Windows API —— функция setCommMask, однако аналогов некоторым флагам прямого вызова не существует.
Функция 工OCTL_SER工AL WA工t_on_mask осуществляет ожидание события, установленного функцией!工octl_ser工al_set_wa工t_mask. Входных параметров нет, а выходной — указатель на переменную типа cardinal, содержащую маску произошедших событий. Аналог в Windows API — WaitCommEvent.
П Функция 工OCTL—SER工al purge очищает выбранные буферы согласно маске, передаваемой в качестве входного параметра ——указателя на переменную типа Cardinal. Маска может состоять из следующих констант:
SERIAL_PURGE_RXABORT ($01) — Все буфера;
SER工AL_PURGE_RXCLEAR ($02) — ВХОДНОЙ буфер;
ser工AL_PURGE_TXABORT($04)— все запросы на передачу данных;
SER工AL_PURGE_TXCLEAR ($08) — ВЫХОДНОЙ буфер.
Аналог В Windows API — функция PurgeComm.
口 Функция ioctl_serial_get_baud_rate возвращает значение текущей скорости обмена. Входных параметров нет, выходной ——указатель на переменную типа Cardinal, содержащую результат. Аналог в Windows API — функция GetCommState.
口 Функция 工octL—Ser工al_get_l工ne_control возвращает характеристики обмена: число стоп-битов, число битов в байте и контроле четности. Входных параметров нет, выходной ——указатель на структуру TSER工AL_L工NE—CONTROL. АНЗЛОГ В Windows API — функция GetCommState.
口 Функция 工octl_ser工AL GET—CHARS возвращает (устанавливает) значения специальных символов, применяющихся для управления обменом: xon, xoff и т. д. Входных параметров нет, выходной ——указатель на структуру TSERIAL_CHARS. Аналог В Windows API — функция GetCommState.
7 Функция ioctl_serial_set_chars устанавливает значения специальных символов, применяющихся для управления обменом: xon, xoff и т. д. Входной параметр —— указатель на структуру tserial_chars, выходных параметров нет. Аналог в Windows API — функция setcommstate.
口 Функция 工octl_ser工al geT—HANdflow возвращает режим управления линиями DTR и RTS. Входных параметров нет, выходной — указатель на
структуру tser 工 AL_HANDFLOW. Аналог в Windows API — функция GetCoiumState.
口 Функция ioctl_serial_set_handflow устанавливает режим управления линиями DTR и RTS. Входной параметр —— указатель на структуру tser工al handflow, выходных параметров нет. Аналог в Windows API — функция SetCoiumState.
口 Функция 工OCTL—SER工al_get_modemstatus возвращает состояние линий модема. Входных параметров нет, выходной — указатель на переменную типа Cardinal, содержащую маску состояния линий модема (табл. 22.2).
Таблица 22.2 Маски состояния модема для ioctl_serial_get_modemstatus
Маска
Значение
Описание
SER工AL_工OC—MCR—DTR
$01
Сигнал DTR активен
SER 工 AL_ 工 OJMCR—RTS
$02
Сигнал RTS активен
SER 工 AL_ 工 OJMCR—OUTl
$04
Сигнал OUT1 активен
SER工AL_工OC_MCR_OUT2
$08
Сигнал OUT2 активен
SER工AL_工OC_MCR_LOOP
$10
Состояние тестирования
Аналог этой команды —— функция GetConraModemstatus, однако она не позволяет получить состояние линий OUT1 и OUT2.
Функция 工octL—Ser工al_get_commstatus возвращает статус последовательного порта. Входных параметров нет, выходной — указатель на структуру TSER工AL—STATUS. АнЗЛОГ В Windows API — функция GetCommState.
口 Функция ioctl_serial_xoff_counter устанавливает счетчик символов xoff. Входной параметр — указатель на структуру tser工al status.
Функция 工octL—Ser工al_get_propert工es возвращает информацию о возможных режимах порта. Входных параметров нет, выходной — указатель на структуру TSER工AL—COMMPROP. Аналог В Windows API — GetCommProp.
口 Функция 工octL—Ser工al_get_dtrrts возвращает информацию о сигналах DTR и RTS. Входных параметров нет, выходной — указатель на переменную типа Cardinal, возвращающую маску флагов ser工al_xxx:
SERIAL_DTR_STATE = $01;
SERIAL_RTS_STATE = $02;
SER工AL_CTS_STATE = $10;
SER 工 AL_R 工 _STATE = $40;
SER工AL_DCD_STATE = $80.
□ Функция 工OCTL SER工AL LSRMST 工nsert включает или выключает вставку информации о статусе линии и статусе модема в принимаемый поток данных.
0 Функция 工ocTL_SER工AL CONF工g_s工ze возвращает размер конфигурационной структуры. Для последовательных портов всегда возвращает 0. Не поддерживается в Windows 2000 и выше,
0 Функция 工OCTL SER工al_get_commconf возвращает конфигурацию последовательного порта.
口 Функция 工OCTL_SER工al_set_commconf工G устанавливает конфигурацию последовател ьного порта.
口 Функция 工OCTL_SER工AL GET—STATS возвращает статистику работы COM-порта. Статистика включает в себя число переданных байтов, число принятых байтов и статистику ошибок. Драйвер самостоятельно увеличивает эти значения при обмене. Входных параметров нет, выходной —— указатель на структуру tser工alperf_stats.
Функция 工OCTL SER工al cleaR—STats очищает статистику обмена по СОМ-порту. Для получения статистики используется функция с кодом IOCTL_SER工AL_GET_STATS. Функция не имеет ни входных, ни выходных параметров.
Функция 工OCTL_SER工al_get_modem_control возвращает состояние регистров модема. Входных параметров нет, выходной — указатель на переменную типа cardinal, содержащую маску флагов ser工al_xxx:
SER 工 AL_ 工 OC_MCR_DTR = $01;
SER工AL_工。C_MCR_RTS = $02;
SER工AL_工OC_MCR_OUT1 = $04;
SERIAL_IOC_MCR_OUT2 = $08;
SER工AL_工OC_MCR_LOOP = $10.
П Функция 工octl_ser工al_set_modem_control устанавливает состояние регистров модема. Входной параметр ——указатель на переменную типа Cardinal, содержащую маску флагов (как в функции iocTL_SER工al_get_modem_control, см. разд. 22,37), выходных параметров нет.
口 Функция 工ocTL_SER工AL_SET_F工fO—COntrol записывает значение регистра fcr (FIFO Control Register). Драйвер не проверяет устанавливаемое значение. Входной параметр ——указатель на переменную типа cardinal, содержащую значение регистра, выходных параметров нет.
Структуры драйвера последовательного порта
Ниже, в листингах 22.2—22.10, приводятся описания структур, используемых для обращения к коммуникационному порту с помощью функции DeviceloControl.
Листинг 22.2. Структура tserial line control
TSER工AL_L工NE_CONTROL = packed record
StopBits : Byte;
Parity : Byte;
WordLength : Byte;
End;
Листинг 22.3. Структура tser工al_queue_s工ze
TSERIAL_QUEUE_SIZE = packed record
InSize : Cardinal;
Outsize : Cardinal;
End;
Листинг 22.4. Структура tserial^timeouts 嘉 X*-““”37»C,“《*“3*W—» 口心■5>”LB“og“'i-・.“z“-og“3~s7»*a“kk"*q、“u“o・2」x,“”2<i・,
TSERIAL_TIMEOUTS = packed record
ReadlntervalTimeout:Cardinal;
ReadTotalTimeoutMultiplier : Cardinal;
ReadTotaITimeoutConstant : Cardinal;
WriteTotalTimeoutMultiplier : Cardinal;
WriteTotalTimeoutConstant : Cardinal;
End;
Листинг 225 Структура tserial_status
TSERIAL_STATUS = packed record
Errors: Cardinal;
HoldReasons : Cardinal;
AmountIninQueue : Cardinal;
AmountInOutQueue : Cardinal;
EofReceived : BOOL;
WaitFor Immediate : BOOL;
End;
{Листинг 妬6* CTpyioypa TSERIAI^HANDFLOW
packed record:Cardinal;:Cardinal;:Cardinal;:Cardinal;packed record:Cardinal;:Cardinal;:Cardinal;:Cardinal;TSERIAL_E1ANDFLOW = Contro IHandSha ke FlowReplace XonLimit XoffLimit
packed record
:Cardinal;
:Cardinal;
:Cardinal;
:Cardinal;
packed record
:Cardinal;
:Cardinal;
:Cardinal;
:Cardinal;
End;
,*f3""*2"“"",?*-y""-4""・".*8-如w№,"*,"**:3,喧***".,,""","干書一"丁-一*
:Листинг 22 J< Структура tserial chars
TSERIAL__CHARS = packed record EofChaг : Byte; ErrorChar : Byte; BreakChar : Byte;
EventChar : Byte;
XonChar : Byte; XoffChar : Byte;
End;
=Листинг 22.8. Структура tser^^^ XoW counter
TSERIAL_XOFE\_COUNTER = packed record
Timeout : Cardinal; // задается в мс
Counter : Longint; // должно быть больше или равно нуля XoffChaг : Byte;
End;
;ЛистингД9.Стру>сгуратзЕК1^
TSERIAL_COMMPROP = packed record
Packet Length
:WORD;
Packetversion
:WORD;
ServiceMask
:Cardinal;
Reservedl
:Cardinal;
MaxTxQueue
:Cardinal;
MaxRxQueue
:Cardinal;
MaxBaud
:Cardinal;
ProvSubType
:Cardinal;
ProvCapabilities
:Cardinal;
SettableParams
:Cardinal;
SettableBaud
:Cardinal;
SettableData
:WORD;
SettableStopParity
:WORD;
CurrentTxQueue
:Cardinal;
CurrentRxQueue
:Cardinal
ProvSpecl
:Cardinal;
ProvSpec2
:Cardinal;
ProvChar
:Pointer;
End;
幫翼:…:"'…"'F•"赛":….:; !::…♦…岁然 二,…."/・…,.时 …
:Лйсггинг 22.10. Cipyigry^! tserialpere^stats
TSERIALPERF_STATS = packed record ReceivedCount : Cardinal;
Transmi11edCount : Cardinal;
FrameErrorCount : Cardinal;
SerialOverrunErrorCount : Cardinal; BufferOverrunErrorCount : Cardinal; ParityErrorCount : Cardinal;
End;
Соответствие функций DeviceloControl и Windows API
В табл. 22.3 приведено соответствие основных кодов функций прямого обращения к драйверу и их аналогов в Windows API.
Таблица 22.3. Таблица соответствия кодов Devi се icCon t го 1 и Windows API
Код
Функция Device!oControl
Аналог Windows API
1
IOCTL_SERIAL_SET_BAUD_RATE
SetCommState
2
IOCTL_SERIAL_SET_QUEUE_SIZE
SetupComm
3
IOCTL_SERIAL_SET_LINE_CONTROL
SetCommState
4
IOCTL_SERIAL_SET_BREAK_ON
SetCommBreak
5
IOCTL_SER工AL_SET_BREAK_OFF
ClearCommBreak
6
工OCTL—SER工AL_工MMED工ATE—CHAR
TransmitCoinmChar
7
IOCTL_SERIAL_SET_TIMEOUTS
SetConunTimeouts
8
IOCTL_SERIAL_GET_TIMEOUTS
GetCommTimeouts
9
IOCTL_SERIAL_SET_DTR
EscapeCommFunct ion
10
IOCTL_SERIAL_CLR_DTR
EscapeCommFunction
11
IOCTL_SERIAL_RESET_DEVICE
EscapeCommFunct ion
12
IOCTL_SERIAL_SET_RTS
EscapeCommFunction
13
IOCTL_SER工AL_CLR_RTS
Es cape ConmiFunct ion
14
IOCTL__SERIAL_SET_XOFF
EscapeCommFunction
15
工。CTL—SER工AL_SET_XON
EscapeCommFunct ion
16
IOCTL_SERIAL_GET_WAIT_MASK
GetComraMask
17
IOCTL_SERIAL_SET_WAIT_MASK
SetCommMask*
18
IOCTL_SERIAL_WAIT_ON_MASK
WaitCommEvent*
19
IOCTL_SERIAL_PURGE
PurgeComm
20
IOCTL_SERIAL_GET_BAUD_RATE
GetCoirunState
21
IOCTL_SERIAL_GET_LINE_CONTROL
GetCommState
22
IOCTL_SERIAL_GET_CHARS
Get ConunState
23
IOCTL_SERIAL_SET_CHARS
SetCommState
24
IOCTL_SERIAL_GET_HANDFLOW
GetCommState
25
IOCTL_SERIAL_SET_HANDFLOW
SetCommState
26
IOCTL_SERIAL_GET_MODEMSTATUS
GetCommModemStatus*
27
IOCTL_SERIAL_GET_COMMSTATUS
GetCommState
28
工OCTL—SER工AL_XOFF_COUNTER
29
IOCTL_SERIAL_GET_PROPERTIES
GetConunProp
30
IOCTL_SERIAL_GET_DTRRTS
Таблица 223 (окончание)
Код
Функция DeviceioControlАналог Windows АР!
31
IOCTL_SERIAL_LSRMST_INSERT
32
工OCTL_SER工AL—CONF工G_S工ZE
33
工 OCTL—SER 工 AL_GET_COMMCONF 工 GGetCoinmConf ig
34
IOCT L_SERIAL_SET_COMMCON FIGSetCommConfig
35
工OCTL_SER工AL_GET一STATS
36
工OCTL—SER工AL_CLEAR_STATS
37
工 OCT L_SERIAL_GET__MODEM_CONTROLGetCommMociemS ta tu s
38
工。CTL—SER工AL_SET_MODEM_CONTROL
39
IOCTL__SERIAL_SET__FIFO_CONTROL
Глава 23
АТ-команды и регистры модемов
В этой главе мы опишем основные команды и регистры модемов. Эта информация будет полезна при программировании или управлении модемами из терминальной программы.
Основные АТ-команды модемов
Команда а/ ——выполнить повторно последнюю команду ——это единственная, не считая Escape-последовательности, команда, перед которой не требуются символы АТ. Эта команда выполняет повтор предыдущей команды.
Команда ата инициирует режим ответа. Модем выходит из режима ожидания и переходит в режим передачи данных (DATA MODE). Через 2 секунды (задержка подтверждения) модем выдаст тональный сигнал ответа. Если после завершения паузы, задаваемой регистром S7, не будет обнаружена несущая, то модем перейдет в состояние ожидания и вернется в холостой режим (IDLE MODE).
Команда атвп: выбор BELL/ITU-T. Эта команда выбирает тип протокола:
атво: выбирает протокол ITU-T V.22;
АТВ1: протокол Bell 212А.
Эти команды допустимы только для скорости передачи 1200 бит/с. По умолчанию выбран протокол Bell 212А (п = 1).
Команда атсп управляет передачей сигнала несущей. Значение п = о запрещает передачу несущей, а значение п = 1 разрешает
Команда atd: набор номера.
Набираемые цифры: 0123456789#*abcd
Разделители: ” ” (пробел)
Модификаторы: т р w @ ! r s / ;,
Команда atd начинает последовательность набора номера. Когда обнаружена команда d, модем ждет 2 секунды и затем начинает набор номера. Модем набирает 12 стандартных тоновых цифр. Эти цифры могут быть разделены символами-разделителями, указанными выше. Модификаторы определяют способ, которым будет осуществляться набор:
Т: ТОНОВЫЙ набор (т. е. ATDT 34 517 98);
Р: импульсный набор(Т. е. ATDP 2 627 50 6).
Все следующие модификаторы используются внутри строки набираемых цифр (например, ATDT 34517W9821 ИЛИ ATDP 26267506).
Модификатор W: ждать наборного тона. Этот модификатор вызовет задержку набора до появления тонального сигнала набора. Если по тайм-ауту, задаваемому регистром S7, не будет обнаружен тональный сигнал набора, вызов будет прерван.
Модификатор “@”: ждать ответной паузы. Если в строке набора будет обнаружен этот символ, модем выдерживает 5-секундную паузу. Если паузы не будет в течение 30 секунд, то вызов будет прерван, и модем вернет код результата no answer — нет ответа.
Модификатор ”!”: ожидание. Этот символ переводит модем в состояние ожидания на 1/2 секунды.
Модификатор "r”: возобновить режим ответа. Если обнаружен этот символ, модем ждет 2 секунды и затем переходит в режим ответа, что эквивалентно команде ата.
Модификатор "s“: набрать хранимый номер. Команда atds вызовет набор номера, сохраняемого в энергонезависимой памяти.
Модификатор "/'*: ждать 1/8 секунды. Эта пауза идентична паузе, вызванной модификатором "запятая”,с той лишь разницей, что длится 1/8 секунды.
Модификатор ";": вернуться в командный режим. Этот символ заставляет модем вернуться в холостой режим (IDLE MODE). Модем остается активизированным.
Модификатор ",": ждать S8 секунд. Этот символ заставляет модем ждать в течение периода, который определяется содержимым регистра S8. Затем модем продолжит набор номера.
Команда атеп: команда эхо. Эта команда управляет режимом возвращения (atei) или невозвращения (атео) командных символов. При включении питания все символы, посылаемые в модем, возвращаются. По умолчанию устанавливается значение п = 1.
Команда atf。: установка режима дуплекса. При п = о устанавливается полудуплексный режим, а при п = 1 ——полнодуплексный режим.
Команда атнп: команда подключения к линии. Параметр п может быть равным 0, 1 или 2. По умолчанию устанавливается 0 (состояние ожидания). При п = 1 (команда athi) выполняется команда *'поднять трубку", а при п = о ——команда "положить трубку". Команда атн2 включена только в целях совместимости и не вызывает никаких действий.
Команда ат工п: команда идентификации. Команда вызывает выдачу идентификационного кода модема. Различные модемы поддерживают различные наборы команд п.
Команда at Ln управляет громкостью звука:
atlo: слабая громкость;
аты: низкая громкость;
ATL2: средняя громкость (по умолчанию);
ATL3: высокая громкость.
Команда атмп управляет работой громкоговорителя:
атмо: громкоговоритель внклюиен всегда;
атм1: громкоговоритель включен в процессе вызова и ответа, т. е. до появления несущей (по умолчанию);
атм2: громкоговоритель включен всегда;
атмз: то же, что и atmi, но не сльипен тон DTMF.
Команда atn задает режим согласования скоростей.
atno: регистр S37 определяет скорость, на которой модем осуществляет соединение с удаленным модемом. Если S37 = о, то скорость соединения совпадает со скоростью передачи данных от компьютера к модему. Если скорость выбирается более, чем из одного стандарта (таких как BELL 212А или CCITT V.22 для скорости 1200 бит/с), то это достигается варьированием команды атв(о, 1).
atni: соединение может происходить на любой скорости, которую поддерживают и локальный, и удаленный модемы. При выборе работы по стандарту CCITT команда в игнорируется.
□ Команда атоп управляет режимом работы модема (по умолчанию п = о).
• атоо. Работа этой команды зависит от текущего режима модема. Если модем находится в командном режиме (COMMAND MODE), команда атоо переведет модем в режим данных (DATA MODE) и инициализирует установление связи на скорости автоматически установленной командой ат. Этой команде должна предшествовать команда athi для выведения модема из режима ожидания. Если модем находится в интерактивном режиме (ON-LINE INTERACTIVE MODE), команда атоо вернет модем в режим DATA MODE.
atoi. Если модем находится в интерактивном режиме (ON-LINE INTERACTIVE MODE), команда atoi запросит настроечную последовательность от другого модема. Эта команда может быть использована, только если модем соединен в режиме V.22bis (2400 бит/с).
口 Команда ATQn: выдача сообщений модемом. Эта команда управляет посылкой ответных сообщений к DTE. Пользователь должен обеспечить время, необходимое для завершения команды. Значение п = о разрешает ответные сообщения, а значение п = 1 — запрещает. По умолчанию п = о. Команда atz сбрасывает режим в режим по умолчанию.
口 Команда ATSr?: запрос s-регистра. Эта команда возвращает десятичную величину, хранящуюся в выбранном s-регистре. Например, для получения содержимого регистра 7 выполните команду ATS7?.
口 Команда ATSr=n: установка регистра. Эта команда используется для загрузки значения в s-регистр. Например, для установки 3 в регистр 0 выполните ATS0 = 3.
口 Команда ATVn: выбор кодов для сообщений модема. Эта команда управляет форматом кода ответа, atvo устанавливает режим 'сжатые цифровые ответы", a atvi ——"развернутые словесные ответы”. По умолчанию п = 1.
口 Команда ATWn устанавливает режим сообщения о состоянии соединения:
atwo: не сообщает скорость соединения и используемый протокол коррекции ошибок;
atwi: сообщает скорость соединения и используемый протокол коррекции ошибок;
atw2: не сообщает скорость соединения и используемый протокол коррекции ошибок. При установке соединенна на экране отображается строка CONNECT н скорость СОМ-иорта.
口 Команда атхп управляет разрешением определенных кодов ответа. Подробности можно найти в документации модема.
口 Команда ATYn: длинный разрыв. Эта команда позволяет пользователю разрешить или запретить (по умолчанию) дополнительную возможность разрыва связи по состоянию ^длинная пауза или длинный пробел" (Long Space).
atyo: запретить длинный разрыв.
atyi: разрешить длинный разрыв. Если модем получает непрерывный сигнал разрыва от удаленного модема в течение более чем 1,6 секунд, модем "зависает*'. Если затем появляется команда атно или DTR прерывает связь (только при задействованной опции &D2), модем разрывает соединение и в течение 4 секунд передает сигнал разрыва, а затем переходит в состояние ожидания.
Команда atz: сброс модема. Эта команда восстанавливает исходное состояние модема в соответствии с параметрами, хранящимися в энергонезависимой памяти. Если модем был в интерактивном режиме (ON_L1NE INTERACTIVE MODE), эта команда прерывает вызов и переводит модем в состояние ожидания.
Команда ат&Сп: управление несущей. Если п = о, сигнал DCD (контакт 8 интерфейса RS-232C) задействован всегда. При п = 1 сигнал DCD включается при обнаружении несущей.
Команда at&dh: управление сигналом DTR (готовность передачи данных):
AT&D0: сигнал DTR всегда задействован;
at&di: модем переходит в командный режим при отсутствии сигнала DTR;
AT&D2: модем переходит в состояние ожидания и возвращается в командный режим при прекращении сигнала DTR;
AT&D3: модем инициализируется при прекращении сигнала DTR.
Команда at&f: загрузка s-регистров из постоянной памяти (заводская установка).
Команда AT&Gn: управление Guard-тоном (сигналом предупреждения):
at&go: нет сигнала предупреждения;
at&gi: Guard-тон 550 Гц;
AT&G2: Guard-тон 1800 Гц.
口 Команда ат&мп выбирает один из режимов работы.
ат&мо: асинхронный режим.
АТ&М1: синхронный режим 1 ——асинхронный вызов и последующий переход в синхронный режим.
АТ&М2 : синхронный режим 2 ——набор хранимого в памяти номера.
ат&мз: синхронный режим 3 —— контроль с помощью сигнала DTR режима иданные/разговор" (Data/Talk Mode). Этот режим позволяет позвонить с параллельного телефонного аппарата при выключенном сигнале DTR, поговорить, после чего перевести модем в режим обмена данных путем включения сигнала DTR.
口 Команда AT&Qn управляет режимом связи.
at&qo: асинхронная связь в прямом режиме. Скорость по СОМ-порту должна совпадать со скоростью связи по телефонной линии.
at&qi: синхронный режим 1 для терминалов, работающих в синхронном и асинхронном режимах. Модем получает команды набора номера в асинхронном виде, а затем переключается в синхронный режим.
При пропадании сигнала DTR или при потере несущей на время, превышающее установленное, модем разрывает соединение и возвращается в асинхронный режим.
AT&Q2: синхронный режим 2 для синхронных терминалов и хранения номеров абонентов. Асинхронный терминал сохраняет или меняет номера телефонов, вводимые по команде &zo в конфигурацию О ПАЗУ. Модем набирает номер при появлении сигнала DTR. Модем разрывает соединение и возвращается в асинхронный режим при пропадании сигнала DTR или при потере несущей на время, превышающее установленное в регистре sio (обычно 1,4 с).
AT&Q3: синхронный режим 3, использует сигнал DTR в режиме переключения "「олос/данные". Оператор вручную набирает номер прм выключенном сигнале DTR, затем включает сигнал DTR, переводя модем в режим данных. Модем разрывает соединение и возвращается в асинхронный режим при пропадании сигнала DTR или при потере несущей на время, превышающее установленное в регистре sio (обычно 1,4 с).
AT&Q4: команда зарезервирована.
AT&Q5: режим коррекции ошибок. Модем устанавливает соединение по протоколу коррекции ошибок. Если соединение по протоколу коррекции ошибок не произошло, то разрыв модемом соединения или установление соединения в нормальном режиме определяется регистром S36.
AT&Q6: асинхронная связь в нормальном режиме. Активизирован буфер модема, позволяющий установить скорость по последовательному порту большую, нежели физическая скорость передачи данных по линии связи.
AT&Q7: команда зарезервирована.
AT&Q8: модем переходит в MNP-режим работы. Если регистр S36 = 1, то модем, находясь в состоянии передачи данных, устанавливает соединение в непосредственном режиме. При ином значении S36 модем устанавливает соединение в MNP-режкме. Если MNP-режим не может быть установлен, то модем переходит в нормальный режим.
AT&Q9: установление соединения по протоколу V.42bis. Если соединение не может быть установлено, модем устанавливает связь по протоколу V.42 (LAP-M). Если и это невозможно, модем переходит в нормальный режим.
Команда at&rh управляет сигналами RTS/CTS в синхронном режиме работы:
at&ro: CTS следует за RTS (по умолчанию);
at&ri: CTS всегда включен.
П Команда at&s: управление сигналом DSR:
at&so: сигнал DSR всегда включен (по умолчанию);
at&si: сигнал DSR используется как обычно.
П Команда дт&тп управляет режимом тестирования:
дт&то: завершить выполняемый тест;
at&ti: запускает локальный аналоговый тест;
АТ&Т2: запускает локальный цифровой тест;
ат&тз: под гота вл ивает модем к выполнению удаленного цифрового теста по запросу другого модема;
АТ&Т4: запрещает удаленный цифровой тест;
АТ&Т5: разрешает удаленный цифровой тест;
Ат&тб: инициализирует удаленный цифровой тест;
АТ&Т7: инициализирует удаленный цифровой тест с самопроверкой.
口 Команда at&v: просмотр текущей конфигурации и пользовательских профайлов модема. По этой команде на экране отобразятся активная конфигурация, пользовательские профайлы, а также хранимые в памяти модема телефонные номера.
П Команда at&w сохраняет текущие значения s-регистров в энергонезависимой памяти, откуда эти установки вызываются при включении питания или командой atz.
口 Команда ат&хп: передача тактовых сигналов. Эта команда используется для выбора источника тактовых сигналов (для штырька 15 интерфейса RS-232C) при синхронной связи:
ат&хо: тактовые сигналы модема (по умолчанию);
AT&xi: тактовые сигналы от DTE:
ат&Х2: модем создает тактовые сигналы передачи из несущей получаемого сигнала.
Команда ат&Уп: выбор активной конфигурации из существующих пользовательских профайлов:
at&yo: загрузка пользовательского профайла 0 в активную конфигурацию;
at&yi: загрузка пользовательского профайла 1 в активную конфигурацию.
П Команда AT&z=n: сохранить телефонный номер в энергонезависимой памяти. Параметр п ——любая строка цифр или набираемых символов не длиннее 32 знаков. Сохраняемый номер может быть набран с использованием команды atds. Можно сохранить только один номер.
23.2. Основные регистры модемов
О Регистр so: количество звонков до ответа (хранится в энергонезависимой памяти).
По умолчанию: 0.
Диапазон: 0…255.
Устанавливает количество звонков, которые модем должен получить перед тем, как начнет процедуру автоответа. Величина 0 выключит автоответ.
□ Регистр si: счетчик звонков.
По умолчанию: 0.
Диапазон: 0...255.
si ——это регистр только для чтения, его значение увеличивается на единицу каждый раз при получении сигнала звонка. Он автоматически очищается, если в течение 8 секунд не поступит ни одного сигнала звонка.
П Регистр S2: символ кода ESC.
По умолчанию: 43, десятичное значение ASCII-символа "+”.
Диапазон: 0...255.
S2 содержит десятичную ASCII-величину символа ESC. Значение больше, чем 127 запретит обнаружение ESC-кода. Этот код позволяет выйти из режима данных (DATA MODE) и перейти в режим интерактивного обмена (ON LINE INTRACTIVE MODE).
□ Регистр S3: символ возврата каретки.
По умолчанию: 13, десятичное значение ASCII-символа "CR”.
Диапазон: 0...255.
содержит десятичную величину символа возврата каретки. Этот символ используется как окончание командной строки, а также как результирующий код.
О Регистр S4: символ перевода строки.
По умолчанию: 10, десятичное значение ASCII-символа "LF".
Диапазон: 0...255.
содержит десятичную величину символа перевода строки. Этот символ выдается после символа возврата каретки, если включен режим полного результата командой atvi.
П Регистр S5: символ возврата на шаг.
По умолчанию: 8, десятичное значение ASCH-символа "BS".
Диапазон: 0...255.
содержит десятичную величину символа возврата на шаг. Во время ввода команды при получении символа “BS" имеет место следующая по- следовател ьность:
символ “BS" возвращается;
на DTE выдается символ пробела (20Н);
другой символ "BS” выдается на DTE.
Если пользователь попытается вернуть на шаг первый командный символ, следующий за ат, то ничего не будет возвращено. Так как каждый символ BS может вызвать посылку на DTE до трех символов, следует принять меры для недопущения передачи символов "BS" один за другим.
П Регистр S6: ожидание тона набора.
По умолчанию: 2.
Диапазон: 0...255.
содержит время в секундах, которое модем будет ждать перед набором телефонного номера. Эта пауза введена для задержки тона набора после выхода из состояния ожидания. Значение S6 игнорируется, если разрешены атхз или АТХ4. Если в S6 установлено значение меньше, чем 2 секунды, модем игнорирует эту величину и ожидает как минимум 2 секунды.
П Регистр S7: ожидание несущей после набора.
По умолчанию: 30.
Диапазон: 0...255.
содержит время в секундах, которое модем будет ждать появления несущей после завершения набора номера. В режиме ответа в S7 содержится время, в течение которого модем ждет появления несущей после посылки тона ответа.
П Регистр S8: время паузы набора номера.
По умолчанию: 2.
Диапазон: 0...255.
содержит время в секундах, в течение которого модем будет ждать при получении запятой в строке набора номера. Эта возможность используется для введения паузы в процессе набора номера.
Регистр S9: время ответа на обнаружение несущей.
По умолчанию: 6 (600 мс).
Диапазон: 0...255 (в 1/10 с).
S9 содержит время в 1/10 с, в течение которого модем будет выдавать тон ответа перед продолжением процедуры соединения. Это позволяет увели-
чить время обнаружения несущей в зашумленных линиях и при нестандартных тоновых сигналах.
П Регистр sio: время ответа на потерю несущей.
По умолчанию: 14 (1,4 с).
Диапазон: 0...255 (в 1/10 с).
sio устанавливает задержку между моментами потери несущей и отключением модема. При увеличении этого времени модем становится менее чувствительным к пропаданию сигналов в линии. При установке в регистр sio значения 255 модем игнорирует статус несущей и функционирует таким образом, как будто несущая присутствует всегда.
Регистр sii: длительность тона нажатия.
По умолчанию: 95 (95 мс).
Диапазон: 0...255 миллисекунд.
S11 содержит длительность в миллисекундах тона нажатия и интервала. Значение по умолчанию вызовет посылку в течение 95 мс тона нажатия и 95-миллисекундный интервал между тонами.
Регистр S12: время ограничения ESC-кода.
По умолчанию: 50 (1 с).
Диапазон: 0...255 (1/50 с).
S12 содержит величину в 20-миллисекундных единицах времени разграничения ESC-кода. Это время, в течение которого модем задерживает расшифровку ESC-последовательности при получении ESC-кода, поэтому задержки прихода следующих символов ESC-последовательности не должны превышать этого времени.
ЧАСТЬ IV
Приложения
Микросхемы UART
Общее описание
WD8250 представляет собой программ ируемый элемент асинхронной связи (АСЕ) в 40-контактном корпусе. Устройство изготавливается по технологии кремниевых затворов N/MOS. АСЕ является программируемым устройством, которое использует двунаправленную 8-битовую шину данных с тремя состояниями.
АСЕ используется для преобразования параллельных данных в последовательный формат со стороны передачи и преобразования поеледовательных данных в параллельные со стороны приемника. П осл едо вате л ьн ы й формат представляет собой стартовый бит для передачи и приема, следующие от 5 до 8 бит данных, бит паритета (если запрограммирован) и один, полтора (только 5~битовый формат) или два стоповых бита. Максимальная рекомендуемая скорость передачи данных 56 Кбит/с. Внутренние регистры дают возможность пользователю программировать различные типы прерываний, сигналов управления модема и знаковые форматы. Пользователь может считывать состояние АСЕ в любое время, читая регистры состояния, прерывания и модема.
Допоянительной характерной особенностью АСЕ является программируемый генератор скорости, который может делить или внутренний еннхронн- зирующий сигнал от кварца, или внешнюю частоту уровня TTL на число от I до (2脩—1).
Характерные особенности
О WD8250 спроектирована для наиболее простого соединения с широко используемыми микропроцессорами (Z-80, 8080А, 6800 и др.)・
口 Полная двойная буферизация.
Независимое управление передачей, приемом, линиями состояния, прерыванием.
Программируемыи генератор скорости передачи позволяет делить любые входные синхроимпульсы на число от 1 до (216 — 1) и производит внутреннюю шестнадцатикратную синхронизацию.
Независимый ввод синхронизирующих импульсов приемника.
口 Полностью программируемый п ослед о вате л ьн ый интерфейс.
П 5-, 6-, 7- или 8-битовые данные.
Формирование бита контроля четности или нечетности и его обнаружение.
Формирование I, 1,5 или 2 стоповых битов.
Формирование скорости в бит/с (канал связи со скоростью до 56 К бит/с).
Обнаружение ложного стартового би га.
口 Полные характеристики сообщений о состоянии.
TTL-драйвер с тремя состояниями для двунаправленной шины данных.
Генерация и обнаружение прерывания передачи.
Характеристи ки внутреннего ди а гносп i рова н и я.
口 Контроль линии связи для обнаружения неисправной изоляции.
Моделирование ошибок прерывания передачи, паритета, переполнения, кадрирования.
Полностью управляемая приоритетная система прерывания.
口 Единственный источник питания +5 В.
Описание контактов
В табл. П1.1 приводится краткое описание контактов UART-микросхемы.
Таблица П1.1. Описание контактов UART-микросхемы
Номер контакта
Обозначение
Сигнал
Функция
От 1 до 8
D0-D7
Шина данных
Линии ввод a/вы вода с тремя состояниями. Двунаправленные линии связи межу WD8250 и шиной данных. Все скомпонованные данные ТХ и RX, управляющие слова и информация о состоянии передаются через шину данных DO—D7
9
RCLK
Вход синхронизации приемника
Этот вход является входом задающей частоты для приемника на ИМС (может быть подсоединен к пятнадцатому контакту BAUDOUT) (частота = скорость в бит/с X 16)
Таблица П1.1 (продолжение)
Номер контакта
Обозначение
Сигнал
Функция
10
SIN
Вход последовательных данных
Ввод получаемых данных из канала связи (от периферийного устройства, модема или устройства сопряжения)
11
SOUT
Выход последовательных данных
Вывод передаваемых данных, предоставляемых в последовательном коде в канал связи. Сигнал SOUT устанавливается в определенное состояние (лог. 1) при общем сбросе
12
CS0
Выбор ИМСО
Когда уровень сигналов С SO и CS1 высокий, a CS2 — низкий, выбирается ИМС. Выбор выполняется, когда строб адреса ADS фиксирует выбранные сигналы ИМС
13
CS1
Выбор ИМС1
14
CS2
Выбор ИМС2
15
BAUDOUT
Выход синхронизации передатчика
16-кратный синхросигнал передатчика ИМС 8250. Частота синхросигнала равна частоте генератора, разделенной на число, загруженное в регистр делителя. Сигнал BAUDOUT может быть использован для синхронизации приемника при подключении его к RCLR (контакт 09)
16
XTAL1
Вход внешнего синхросигнала
К этим контактам подключают кварц или внешний задающий генератор для установки требуемой скорости передачи
17
XTAL2
Выход внешнего синхросигнала
18
DOSTR
Вход строба вывода данных
Когда ИМС выбрана, низкий или высокий уровни сигнала DOSTR позволяют запомнить данные в выбранном регистре WD8250 (записывает ЦПУ). Только одна из этих линий должна использоваться, а вторая должна быть установлена в неактивное состояние
19
DOSTR
20
Vss
Заземление
Заземление
Таблица П1.1 (продолжение)
Номер контакта
Обозначение
Сигнал
Функция
21
22
DISTR
DISTR
Вход строба ввода данных
Когда ИМС выбрана, низкий или высокий уровень сигнала DISTR позволяет считывать с выбранного регистра WD8250 (читает ЦПУ). Только одна из этих линий может быть использована, а вторая должна быть установлена в неактивное состояние
23
DDIS
Выход блокировки буфера данных
Выход имеет низкий уровень сигнала всякий раз, когда данные считываются с WD8250. Может быть использован для изменения направления данных от внешнего приемопередатчика
24
CSOUT
Выход выбора ИМС
Выход имеет высокий уровень сигнала всякий раз, когда ИМС выбрана. Передача данных в линию не может быть инициирована до тех пор, пока уровень сигнала CSOUT высокий
25
ADS
Вход строба адреса
При низком уровне сигнала обеспечивается фиксирование сигналов для выбора регистра (АО, А1, А2) и выбора ИМС (CSO, CS1.CS2). Примечание: положительный фронт сигнала ADS необходим, когда сигналы выбора регистра (АО, А1, А2) и выбора ИМС (СSO, CS1.CS2) нестабильны во время операции считывания или записи. Если это не требуется, уровень сигнала входа ADS может быть постоянно низким
26
А2
Выбор регистра А2
27
А1
Выбор регистра А1
Эти 3 входа используются для выбора внутреннего регистра WD8250 во время считывания и записи
28
АО
Выбор регистра АО
29
NC
Нет подсое ди не-
Не используется
ния
Таблица П1.1 (окончание)
Номер контакта
Обозначение
Сигнал
Функция
30
INTRPT
Выход прерывания
Уровень сигнала выхода высокий всякий раз, когда присутствует разрешенное прерывание
31
OUT2
Выход 2
Выход, предназначенный для пользователя, который может быть запрограммирован третьим битом регистра управления модема. Бит, равный 1, формирует низкий уровень сигнала OUT2
32
RTS
Выход запроса передачи
Когда уровень сигнала выхода низкий, он сообщает модему или устройству сопряжения, что WD8250 готов передать данные. См. регистр управления модема
33
DTR
Выход готовности терминала данных
Если выход имеет низкий уровень сигнала, он сообщает модему или устройству сопряжения, что WD8250 готов получить данные
34
OUT1
Выход 1
Выход, предназначенный для пользователя, может быть запрограммирован вторым битом регистра управления модема. Бит, равный 1, формирует низкий уровень сигнала OUT 1
35
MR
Вход общего сброса
Высокий уровень сигнала сбрасывает регистры в начальные состояния
36
CTS
Вход готовности для передачи
Сигнал с АПД, указывающий, что устройство готово к передаче данных. См. регистр состояния модема
37
DSR
Вход готовности устройства сопряжения
Сигнал с АПД, используемый для указа* ния состояния готовности устройства сопряжения. См. регистр состояния модема
38
RSLD
Вход детектора принимаемого линейного сигнала канала данных
Сигнал с АПД, указывающий, что идет получение сигнала, который удовлетворяет условиям качества сигнала. См. регистр состояния модема
39
RI
Вход индикатора вызова
Вход имеет низкий уровень сигнала, если сигнал вызова получен модемом или устройством сопряжения. См. регистр состояния модема
40
Vcc
+5 В
Питание +5 В
Типы микросхем UART
В персональных компьютерах используется несколько типов микросхем, совместимых с 8250:
口 8250 — первая версия UART;
口 16450 — новая версия, но по существу та же 8250;
О 16550 ——имеет внутренний FIFO-буфер для приемника и передатчика, но реализованный с ошибками;
口 16550А — то же, что 16550. но ошибки в FIFO исправлены.
Windows определяет тип микросхемы автоматически, а в DOS-режиме можно использовать такой алгоритм:
П проверка микросхемы 8250:
сохранить значение регистра расширения (адрес BasePort+7);
записать случайное число (не равное считанному значению) в регистр расширения;
считать регистр расширения;
если число не сохранилось, это микросхема 8250:
восстановить значение регистра расширения;
口 проверка микросхемы 16550:
сохранить значение регистра определения прерывания ier (адрес BasePort+2);
включить режим FIFO (записать в 工er значение 1);
прочитать значение регистра ier;
если бит ier[6] равен 1, то это микросхема 16550А;
если бит ier[7] равен 1, то это микросхема 16550;
если предыдущие тесты не прошли, это микросхема 16450.
Windows NT/2000 автоматически включает использование FIFO, если обнаруживает микросхему, которая корректно поддерживает работу в этом режиме. Для ручного управления режимом FIFO следует отключить опцию Use FIFO buffers (Использовать FIFO) в окне настроек порта (см. рис. 13.3) или воспользоваться редактором реестра (см. разд. 5.4).
Библиотека функций для Pascal
В нашей книге мы использовали дополнительные функции, такие как преобразование числа в шестнадиатеричное представление, преобразование строки в число и т. д. В Delphi для таких преобразований уже существуют ФУНКЦИИ StrToInt, StrToIntDef, IntToHex, IntToStr И T. Д., а ДЛЯ ЯЗЫКЭ Pascal аналоги таких функций приведены в листинге П2.1.
■*"*罗瑟,干* "茨基0努"£,畧,—》.…謀..叶.号笠「,?,選?…好2……:"?
Листинг П2Л. Функции преобразования для Pascal (DOS)
-"■;・Х・;Ш■"二■»■ = ■*-”:**■“: 士“♦-厶訂・・;“”:£”・・・”*3。“:--~;»含.■**•--”-”;、■•■♦* —-“-•-♦““..■•.•"-“♦♦••“♦••■-•*;W“-gCE«»-*
{ Преобразование байта в шестнадцатеричную строку }
Function Byte2Hex(В: Byte):String;
Const hStr : String ='0123456789ABCDEF';
Begin
Byte2Hex:= hStr[(B div 16)+1]+ hStr[(B mod 16)+1];
End;
{ Преобразование длинного целого в шестнадцатеричную строку }
Function Long2Hex(В: Longint):String;
Begin
Long2Hex:= Byte2Hex(B div $100)+ Byte2Hex(B and $0FF);
End;
{ Преобразевание строки в целое число. Если преобразование }
{ невозможно, возвращается DefValue}
Function StrToIntDef(S : String; DefValue : Long):Long;
Var ErrCode : Int; Value :Long;
Begin
Vai(S, Value, ErrCode);
If ErrCode <> 0 then Value:= DefValue;
StrToIntDef:= Value;
End;
{ Преобразование строки в Single-число. Если преобразование }
{ невозможно, возвращается DefValue}
Function StrToFloatDef(S : String; DefValue : Single):Single;
Var ErrCode : Int; Value : Single;
Begin
Vai(S, Value, ErrCode);
If ErrCode <> 0 then Value:= DefValue;
StrToFloatDef:= Value;
End;
Формат SINGLE чисел с плавающей точкой
В отличие от байтов, слов и строк, формат чисел с плавающей точкой может иметь различные реализации. Так, в Borland Pascal имеется формат single, занимающий 4 байта; формат Real, занимающий 6 байт и формат Extended, занимающий 10 байт. В языке С также имеется несколько форматов представления чисел с плавающей точкой. Со стороны устройств, подключаемых к компьютеру, формат дробных чисел зависит от процессора и реализации его библиотек (стандартных или написанных самостоятельно).
Чаще всего микропроцессорные устройства используют 4-байтовое представление чисел с плавающей точкой ——single (IEEE-стандарт чисел с плавающей точкой одинарной точности).
Битовое представление чисел типа single приведено в табл. П3.1.
Таблица /73.7. Битовое представление чисел типа single
Номер бита
Описание
31
30-23
22-0
Бит знака (s) Порядок (р) Мантисса (т)
Значение числа вычисляется по формуле:
(-1) х 2P —【27 * 掰.
Нулевое значение представлено четырьмя нулевыми байтами. Диапазон, задаваемый этим типом, приблизительно от 1Е~38 до 1Е+38.
Для портов существует только функция передачи байта, поэтому для передачи числа типа single необходимо реализовать функцию, поеледовательно передающую байты числа. Очевидно, что можно реализовать эту функцию двумя способами: либо посылая байты в порядке 0, 1, 2, 3, либо в порядке 3, 2, I, 0. Ясно, что эти способы эквивалентны, важно лишь, чтобы приемник и передатчик работали по одинаковому протоколу (см. прил. 8).
Описание функций Windows для использования в Visual Basic
При необходимости применения описанных в данной книге функций на языке Visual Basic следует использовать их описания, приведенные в листинге П4.1.
Листинг П4.1. Описание функций коммуникационного порта для Visual Basic
Declare Function ReadFileEx Lib "kernel32” Alias "ReadFileEx" (By Vai hFile As Long, IpBuffer As Any, By Vai nNumberOfBytesToRead As Long, IpOverlapped As OVERLAPPED, ByVai IpCompletionRoutine As Long) As Long
Declare Function WriteFileEx Lib "kernel32** Alias ^WriteFileEx** (ByVai hFile As Long, IpBuffer As Any, By Vai nNumberOfBytesToWrite As Long, IpOverlapped As OVERLAPPED, ByVai IpCompletionRoutine As Long) As Long
Declare Function BuildCommDCB Lib "kernel32м Alias nBui1dCommDCBAn (ByVai IpDef As String, IpDCB As DCB) As Long
Declare Function Bui1dCommDCBZ\ndTimeouts Lib "kernel32" Alias "BuiIdCommDCBtodTimeoutsA" (ByVai IpDef As String, IpDCB As DCB, IpCcramTimeouts As COMMTIMEOUTS) As Long
Declare Function SetCommBreak Lib ,,kernel32n Alias "SetCommBreakM (ByVai nCid As Long) As Long
Declare Function ClearCommBreak Lib "kernel32" Alias ,,ClearCoiranBreakH (ByVai nCid As Long) As Long
Alias nClearComixiErrorH As CO4STAT) As LongAlias nClearComixiErrorH As CO4STAT) As LongDeclare Function ClearCommError Lib "kernel32** (ByVai hFile As Long, IpErrors As Long, IpStat
Alias nClearComixiErrorH As CO4STAT) As Long
Alias nClearComixiErrorH As CO4STAT) As Long
Declare Function EscapeCommFunction Lib ,,kernel32n Alias "EscapeCommFunction,, (By Vai nCid As Long, ByVai nFunc As Long) As Long
Declare Function GetCommMask Lib **kernel32м Alias nGetCommMasк*' (ByVai hFile As Long, IpEvtMask As Long) As Long
Declare Function CreateEvent Lib ,,kernel32H Alias ,(CreateEventAH
(IpEventAttributes As SECURITY_ATTRIBUTES, ByVai bManualReset As Long, By Vai blnitialState As Long, ByVai IpName As String) As Long
Declare Function WaitCommEvent Lib (ByVai hFile As Long, IpEvtMask As LongDeclare Function WaitCommEvent Lib (ByVai hFile As Long, IpEvtMask As Long,,kernel32,* Alias "WaitCorranEvent"Long, 1pOver1apped As OVERLAPPED) As,,kernel32,* Alias "WaitCorranEvent"Long, 1pOver1apped As OVERLAPPED) As
Declare Function WaitCommEvent Lib (ByVai hFile As Long, IpEvtMask As Long
Declare Function WaitCommEvent Lib (ByVai hFile As Long, IpEvtMask As Long
,,kernel32,* Alias "WaitCorranEvent"
Long, 1pOver1apped As OVERLAPPED) As
,,kernel32,* Alias "WaitCorranEvent"
Long, 1pOver1apped As OVERLAPPED) As
Declare Function GetCommConfig Lib иkernel32" Alias "GetCommConfigM (ByVai hCommDev As Long, IpCC As COMMCONFIG, IpdwSize As Long) As Long
Declare Function SetCcanmConfig Lib **kernel32H Alias ,fSetCommConfig*' (ByVai hCommDev As Long, IpCC As CCMdCONFIG, By Vai dwSize As Long) As Long
Declare Function CowmConfigDialog Lib ”kemel32" Alias
^CommConfigDialogA" (By Vai IpszName As String, By Vai hWnd As Long, IpCC As CCMMCONFIG) As Long
Declare Function GetCommProperties Lib "kernel32" Alias
"GetCommProperties" (ByVai hFile As Long, IpCommProp As COMMPROP) As Long
Declare Function GetCcmmState Lib "kernel32м Alias uGetCommState,f (ByVai nCid As Long, IpDCB As DCB) As Long
Declare Function SetCommState Lib nkernel32" Alias *'SetCoramState,f (ByVai hCommDev As Long, IpDCB As DCB) As Long
Declare Function GetCommTimeouts LibDeclare Function GetCommTimeouts Lib,,kernel32,* Alias,,kernel32,* AliasMGetConimT imeout s "MGetConimT imeout s "(ByVai hFile As Long, IpCcmmTimeouts(ByVai hFile As Long, IpCcmmTimeoutsAs COMMTIMEOUTS)As COMMTIMEOUTS)As LongAs Long
Declare Function GetCommTimeouts Lib
Declare Function GetCommTimeouts Lib
,,kernel32,* Alias
,,kernel32,* Alias
MGetConimT imeout s "
MGetConimT imeout s "
(ByVai hFile As Long, IpCcmmTimeouts
(ByVai hFile As Long, IpCcmmTimeouts
As COMMTIMEOUTS)
As COMMTIMEOUTS)
As Long
As Long
” SetCommT imeout s ** As Long” SetCommT imeout s ** As Long
” SetCommT imeout s ** As Long
” SetCommT imeout s ** As Long
Declare Function SetCommTimeouts Lib "kerne工32" Alias (ByVai hFile As Long, 1pCommTimeouts As COMMTIMEOUTS)
Declare Function PurgeComm Lib ,,kernel32n Alias "PurgeComm" (ByVai hFile As Long, ByVai dwFlags As Long) As Long
Declare Function SetupComm Lib ,,kernel32" Alias "SetupComm" (ByVai hFile As Long, ByVai dwInQueue As Long, ByVai dwOutQueue As Long) As Long
Declare Function GetDefaultCommConfig Lib '*kernel32n Alias "GetDefaultCommConfigAM (ByVai IpszName As String, IpCC As COMMCONFIG, IpdwSize As Long) As Long
Declare Function SetDefaultCommConfig Lib 11 kernel32м Alias
MSetDefaultCommConfigA" (ByVai IpszName As String, IpCC As COMMCCWFIG, ByVai dwSize As Long) As Long
Declare Function TransmitCoinmChar Lib "kemel32" Alias HTransmitCorranChar" (ByVai nCid As Long, By Vai cChar As Byte) As Long
Использование
ActiveX-компонента MSComm32
В Windows включен специальный компонент Mscomm32.ocx, позволяющий работать с коммуникационным портом из любого приложения, поддерживающего ActiveX.
Важно
В Windows 95/98 вызов MSCoram. PortOpen приводит к утечке памяти в размере примерно 4 Кбайт на 100 вызовов. Эта ошибка исправлена в Visual Studio 97 SP2.
В листинге П5.1 показана работа с этим компонентом на языке Visual Basic.
罩驾号.Т..工:/…;f.:?,.:.…: ..^.....>,..,.....,..;.. ..手: …"""一.:.,..
:Листинг П5.1. Реализация передатчика с помощью MSCoxam32
• РЕАЛИЗАЦИЯ ПЕРЕДАТЧИКА (MSDN, Q158008)
Основные описания
Dim Offset As Long
Dim FileData As Byte
Dim Fil eName As String
Для работы этой программы свойства компонента MSCornml должны быть установлены в следующие значения:
RThreshold = 1
SThreshold = 3
'обработчик Load для Form
* 28800 baud, no parity, 8 data, 1 stop bit.
'Для RS-232 максимальная скорость 28 800
Private Sub Form_Loaci ()
' Открыть порт MSComml.Settings = "28800,N,8,1" MSComml.InputLen = 1 MSComml.CommPort = 2
MSCcmml.PortOpen = True
* Имя файла для передачи FileName = "d:\sample.hlp"
,Открыть файл
Offset — 1
Open FileName For Binary Access Read As #1 End Sub
'Обработчик события ONCOMM для Comm-компонента
'Свойство CommEvent принимает значение 2 при готовности 'передатчика к передаче следующего символа
Private Sub MSCoinml_OnCoinm ()
If MSComml.CommEvent = 2 Then temp = MSComml. Input
If Offset <= FileLen(FileName) Then
Get #1, Offset, FileData q = FileData
MSComml.Output = Format(q, "000”) Offset = Offset + 1
End If
End If
End Sub
* Обработчик UnLoad для Form — закрыть файл Private Sub Form_Unload(Cancel As Integer) Close #1
End Sub
РЕАЛИЗАЦИЯ ПРИЕМНИКА
Основные описания
Dim ByteCount As Long Dim FileData As Byte
* Для работы программы свойства MSComml должны быть изменены так: 1 RThreshold = 3
'Обработчик Load для Form
'28800 baud, no parity, 8 data, 1 stop bit.
Private Sub Forni_Load ()
MSComml.Settings = "28800,N,8,1"
MSComml.InputLen = 3
MSComml.CorranPort = 1
MSComml.PortOpen = True
'Открытие файла для сохранения полученных данных Open "с:\sanple.txt" For Binary Access Write As #1 End Sub
'Обработчик OnConim
Private Sub MSCorranl_OnComm ()
If MSComml. CorumEvent = 2 And MSComml. InBufferCount > 0 Then FileData = CInt(MSComml.Input) ByteCount = ByteCount 十 L Put #1, ByteCount, FileData MSComml.Output = Chr$(26)
End If End Sub 'Событие CLICK по кнопке Private Sub Commandl_Click()
MSComml.Output = Chr$(26) End Sub
'Обработчик UnLoad для Form
Private Sub Form_Unload(Cancel As Integer) Close #1
End Sub
Сервис BIOS — обслуживание INT14H
В гл. 6 и 18 мы использовали функции сервиса BIOS, а в листинге П6.1 мы приводим пример реализации самого сервиса.
Листинг П6.1. Реализация INT14H
;——INT 14
;RS232_IO
;ЭТА ПРОГРАММА ОБСЛУЖИВАЕТ RS232
; ООН иниииализировать коммуникационный порт
;01Н послать символ через выбранный порт RS-232
;02Н получить символ через выбранный порт RS-232
; ОЗН дать статус порта связи
;СОМ1 м COM2 инициализирует BIOS.
;при инициализации заполняются следующие ячейки
;адрес регистра данных передатчика СОМ1
;0:400 RS232_BASE[。]DW 3F8H
; адрес регистра данных передатчика COM2
;0:402 RS232_BASE[2]DW 2F8H
; Время ожидания прихода символа для ССМ1
;0:47СRS232_TIM_OUT[0]DB01
;0:47DRS232_T工M_OUT[1]DB01
;0:47ERS232_T工M_OUT[2]DB01
;0:47FRS232_T工M_OUT[3]DB01
;Для инициализации COM3 и;Для инициализации COM3 иCOM4 необходимо записать адрес в
;Для инициализации COM3 и
;Для инициализации COM3 и
;0:404 RS232_BASE ⑷ DW ?
;0:406
RS232_BASE[6] DW ?
;и использовать функцию АН = О
ASSUME
CS:CODE,DS:DATA
ORG
0E729H
Al LABEL WORD ; ТАБЛИЦА ЗНАЧЕНИЙ ДЛЯ ННИЦИАЛНЗАЦИИ
DW
1047; 110 БОД
DW
768; 150
DW
384; 300
DW
192; 600
DW
96; 1200
DW
48; 2400
DW
24; 4800
DW
12; 9600
RS232_IO
PROCFAR
;—вход
STI
В ПРЕРЫВАНИЕ
PUSH
DS
PUSH
DX
PUSH
SI
PUSH
DI
PUSH
CX
PUSH
BX
MOV
SI,DX ; В SI НСЖЕР СОМ-ПОРТА ИСПОЛЬЗУЕТСЯ
;ДЛЯ АДРЕСАЦИИ к ЯЧЕЙКАМ RS232_BASE
MOV
DI,DX ; DI ИСПОЛЬЗУЕТСЯ ДЛЯ АДРЕСАЦИИ К ;RS232_T工М_ОиТ
SHL
SI,1
CALL
DDS
MOV
DX,RS232_BASE[SI]; ПОЛУЧЕНИЕ БАЗОВОГО АДРЕСА
OR
DX,DX; ТЕСТ НЕ ИНИЦИАЛИЗИРОВАННОГО ПОРТА
JZ
АЗ; ВОЗВРАТ
OR
АН, АН; ТЕСТ ДЛЯ (АН) = 0
JZ
А4; ИНИЦИАЛИЗАЦИЯ
DEC
АН; ТЕСТ ДЛЯ (АН)= 1
JZ
А5; ПЕРЕСЫЛКА AL
DEC
JZ
AH
Al 2
;ТЕСТ ДЛЯ (АН)=2
;ЧТЕНИЕ В AL
А2:
DEC
AH
;ТЕСТ ДЛЯ (АН)=3
JNZ
A3
JMP
Al 8
;ПОЛУЧЕНИЕ СТАТУСА
АЗ:
;ВЫХОД
POP
BX
POP
ex
POP
DI
POP
SI
POP
DX
POP
DS
IRET
ИНИЦИАЛИЗАЦИЯ
A4 :
MOV
AH, AL;
ПАРАМЕТРЫ ИНИЦИАЛИЗАЦИИ В АН
ADD
DX,3;
В DX - 3FBH (2FBH) АДРЕС РЕГИСТРА
УПРАВЛЕНИЯ ЛИНИЕЙ LCR
MOV
AL,8OH
OUT
DX,AL;
УСТАНОВКА DLAB=1 ДЛЯ ПОЛУЧЕНИЯ
ДОСТУПА К ФИКСАТОРАМ ДЕЛИТЕЛЯ
ОПРЕДЕЛЕНИЕ СКОРОСТИ ПЕРЕДАЧИ
MOV
DL,AH;
ВЫДЕЛЕНИЕ ИЗ БАЙТА ННИЦИАПИЗАЦИИ
MOV
CL, 4;
СКОРОСТИ ПЕРЕДАЧИ.
ROL
DL, CL
AND
DX,OEH
MOVDI,OFFSET Al
ADDD工,DX
MOVDX,RS232—BASE[SI]
INCDX
MOVAL,CS:[DI]+1
OUTDX,AL
DECDX
MOVAL,CS:[D工]
OUTDX,AL
ADDDX, 3
; ПОЛУЧЕНИЕ СМЕЩЕНИЯ ТАБЛИЦЫ ДЕЛИТЕЛЯ
;ПОЛУЧЕНИЕ БАЗОВОГО АДРЕСА
; АДРЕС СТАРШЕГО Б7ХЙТА РЕГИСТРА ДЕЛИТЕЛЯ
; ПОЛУЧЕНИЕ ВЕЛИЧИНЫ ДЕЛИТЕЛЯ
; ЗАПИСЬ СТАРШЕГО БАЙТА
; ЗАПИСЬ МЛАДШЕГО БАЙТА
;РЕГИСТР УПРАВЛЕНИЯ МОДЕМОМ
MOV
AL, AH
;ПАРАМЕТРЫ
AND
AL,01FH
; ОБНУЛЕНИЕ 7,6 И 5 БИТ
OUT
DX,AL
;ЗАПИСЬ ПАРАМЕТРОВ
DEC
DX
DEC
DX
;РЕГИСТР ИДЕНТИФИКАШ1И ПРЕРЫВАНИЙ
MOV
AL,0
OUT
DX,AL
;ВСЕ ПРЕРЫВМИЯ ВЫКЛЮЧЕНЫ
JMP
SHORT Al8
;ССМ STATUS
ПЕРЕСЫЛКА СИМВОЛА АН =
=1
А5:
PUSH
AX
ADD
DX, 4
;РЕГИСТР УПРАВЛЕНИЯ МОДЕМОМ
MOV
AL, 3
;ВЫХОД DTR И RTC В АКТИВНОМ СОСТОЯНИИ
OUT
DX,AL
INC
DX
;РЕГИСТР СОСТОЯНИЯ МОДЕМА
INC
DX
MOV
BH,30H
;ОЧИСТКА ДЛЯ ПЕРЕДАЧИ И ГОТОВНОСТЬ
;ПЕРЕДАЧИ
CALL
WAIT_FOR_STATUS
;ОЖИДАНИЕ
JE
A9
;ДА, ПЕРЕДАЧА СИМВОЛА
A7:
POP
CX
MOV
AL, CL
;ПЕРЕЗАГРУЗКА БАЙТА ДАННЫХ
A8:
OR
AH,80H
;ИНДИКАЦИЯ TIME OUT
JMP
A3
;ВЫХОД
A9:
;CLEAR_TO_SEND
DEC
DX
;РЕГИСТР СОСТОЯНИЯ ЛИНИИ
A10:
;WAIT_SEND
MOV
BH,20H
;РЕГИСТР ДАННЫХ ПЕРЕДАТЧИКА ПУСТ
CALL
WAIT_FOR_STATUS
JNZ
A7
;ВОЗВРАТ С УСТАНОВЛЕННЫМ TIME OUT
All:
;OUT_CHAR
SUB
DX, 5
;ПОРТ ДАННЫХ
POP
CX
;ВОЗВРАЩЕНИЕ ДАННЫХ
MOV
AL, CL
OUT
DX,AL
;ВЫВОД СИМВОЛА
JMP
A3
;выход
ЧТЕНИЕ AH = 2
А12:
ADD
DX,4;
РЕГИСТР УПРАВЛЕНИЯ МОДЕМОМ
MOV
AL,1;
ПЕРЕВОД DTR В АКТИВНОЕ СОСТОЯНИЕ
OUT
DX,AL
INC
DX;
РЕГИСТР СОСТОЯНИЯ МОДЕМА
INC
DX
A13:
WA 工 T_DSR
MOV
BH,20H;
ГОТОВНОСТЬ К ПЕРЕДАЧЕ ДАННЫХ
CALL
WAIT_FOR_STATUS ;
JNZ
A8;
ВОЗВРАТ С ОШИБКОЙ
A15:
WA 工 T—DSR—END
DEC
DX;
РЕГИСТР СОСТОЯНИЯ линии
A16:
WAIT_RECV
MOV
BH,1;
ИНДИКАТОР ГОТОВНОСТИ ДАННЫХ В ПРИЕМНИКЕ
CALL
WAIT_FOR_STATUS
JNZ
A8;
TIME OUT ОШИБКА
A17:
GET—CHAR
AND
AL,00011110B
;ТЕСТ ОШИБОЧНОГО СОСТОЯНИЯ
MOV
DX,RS2 32_BASE[SI]
;ПОРТ ДАННЫХ
IN
AL,DX
;ЧТЕНИЕ СИМВОЛА
JMP
A3
;ВЫХОД
СТАТУС AH = 3
A18:
MOV
DX, RS232_BASE [S口
ADD
DX,5
;РЕГИСТР СОСТОЯНИЯ ЛИНИИ
IN
AL,DX
MOV
AH, AL
;ПЕРЕВОД В АН ДЛЯ ВОЗВРАТА
INC
DX
;РЕГИСТР СОСТОЯНИЯ МОДЕМА
IN
AL,DX
JMP
A3
;ВОЗВРАТ
;ОЖИДАНИЕ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ
;ВХОД :
;ВН=МАСКА
;DX=AHPEC РЕГИСТРА СТАТУСА
;ВЫХОД:
ZF УСТАНОВЛЕН = ВРЕМЯ ВБ5ШЛО
АН=СТАТУС
WAIT_FOR_STATUS PROC
NEAR
MOVBL,RS232_TIM_OUT[DI] ; ЧТЕНИЕ СЧЕТЧИКА
WFSO:
SUBex, ex
WFS1:
INAL, DX
;ПОЛУЧЕНИЕ СТ АУСА
MOVAH, AL
;СОХРАНЕНИЕ В АН
ANDAL,BH
;ВЬЩИЛЕНИЕ БИТОВ ДЛЯ ТЕСТА
CMPAL, BH
;СРАВНЕНИЕ С МАСКОЙ
JEWFS—END
;ВЫХОД С ОБРОНЕННЫМ ZF
LOOPWFS1
DECBL
JNZWFSO
;ПОВТОРИТЬ СНОВА
ORBH,BH
WFS—END:
RET
WA工T_FOR_STATUS ENDP
RS232 IOENDP
;УСТАНОВИТЬ ZF
Как работает GivelO
Для объяснения принципа работы драйвера GivelO нам придется сказать несколько слов о реализации защиты ввода/вывода (в/в) в Windows NT/ 2000/ХР.
Основа защиты в/в ——четырехуровневая система привилегий процессора 80x86. Наиболее привилегированный уровень — уровень 0, наименее привилегированный —— уровень 3. Операционная система Windows использует только два крайних уровня:
口 уровень 0 для режима ядра с полным доступом (fiill access kernel mode);
П уровень 3 для ограниченного режима пользователя (user mode).
Текущий уровень привилегий (CPL, Current Privilege Level) хранится в двух младших битах регистра cs.
Защита в/в имеет два уровня проверок.
В битах 12 и 13 регистра eflags процессор хранит уровень привилегий, для которого разрешен прямой доступ к портам в/в, называемый IOPL (I/O Privilege Level). ОС Windows всегда устанавливает IOPL равным нулю, поэтому программы, выполняющиеся на уровне ядра (уровень 0), всегда имеют прямой доступ к портам в/в, а программы, выполняющиеся в режиме пользователя (уровень 3), проходят вторую стадию.
Вторая стадия проверки защиты реализуется с помощью карты разрешения в/в (1ОРМ, I/O Permission Мар), представляющей собой битовый массив, каждый бит которого соответствует порту в/в. Если бит установлен в 1, доступ запрещен, и при доступе к соответствующему порту произойдет исключение. Если бит установлен в 0, то к данному порту предоставлен прямой и беспрепятственный доступ. Таблица ЮРМ хранится в структуре "сегмент состояния задачи" (TSS, Task State Segment) в основной памяти, в сегменте, на который ссылается селектор сегмента в регистре задачи (TR, Task Register) процессора. Смещение ЮРМ внутри TSS хранится в 2-бай- товом числе по смещению 0x66 в TSS. Следует отметить, что хотя адресное пространство в/в процессоров семейства 80x86 включает 65 536 8-битовых
портов, таблица ЮРМ не обязательно должна содержать 8192 байта: массив всегда начинается с адреса 0, но есть возможность не предоставлять битовую маску для старших адресов. Любая, непредоставленная часть битового массива считается равной 1, следовательно, доступ к этим портам будет запрещен.
В библиотеке функций, осуществляющих поддержку драйверов режима ядра (ntoskrnl), содержатся три недокументированные функции.
口 Ke386SetioAccessMap()принимает 2 аргумента: параметр типа integer, который нужно установить в 1, чтобы функция работала, и указатель на буфер. Функция копирует переданную карту доступа к в/в длиной 0x2000 байт из буфера в TSS по смешению 0x88.
□ Ke38 6QueryioAccessMap() принимает такие же аргументы, но делает противоположное, копируя текущую таблицу ЮРМ из TSS в буфер длиной 0x2000 байт.
口 Ke386ioSetAccessProcess() принимает 2 аргумента: указатель на структуру процесса, полученный ВЫЗОВОМ PsGetCurrentProcess () , И Целое число, которое должно быть установлено в 1 для разрешения доступа к в/в, или в 0 для его запрещения. Когда целочисленный аргумент равен 0, функция запрещает доступ к в/в установкой смещения ЮРМ переданного процесса за границу сегмента TSS. Когда целочисленный аргумент равен 1, функция разрешает доступ к в/в, устанавливая смещение ЮРМ переданного процесса на начало ЮРМ по смещению 0x88 в TSS.
Используя эти функции, можно читать, изменять и записывать обратно таблицу ЮРМ, предоставляя доступ к нужным портам установкой соответствующих битов в 0.
Драйвер Робертса GivelO, код которого приведен в листинге П7.1, устанавливает все биты таблицы ЮРМ в нули, открывая полный доступ к портам для режима пользователя. Следующий листинг (П7.1) —— пробное приложение, называющееся TestlO. В нем используется прямой доступ к системному динамику для демонстрации прямого обращения к портам в/в. Следует отметить, что основной задачей драйвера является изменение таблицы ЮРМ. поэтому сам драйвер для работы не требуется. В тестовом примере драйвер выгружается сразу же после открытия.
Листинг П7.1. Драйвер GivelO (Dale Roberts)
/*
GivelO — создан Dale Roberts
Компиляция: Используйте средство DDK BUILD
Назначение: Предоставить доступ к прямому в/в для процесса режима пользователя
*/
#include <ntddk.h>
#include <mondebug.h>
/* Имя нашего драйвера устройства */
#define DEVICE_NAME_STRING L"giveio"
/*
Это "структура** 10№. Это просто массив байтов размером 0x2000. Он содержит 8К * 8 бит = 64 Кбит IOPM, которые покрывают все 64- килобайтное адресное пространство в/в х86 процессора. Каждый нулевой бит предоставляет доступ к соответствующему порту для user-mode-процесса. Каждый единичный бит запрещает доступ к в/в через соответствующий порт.
*/
#define 工。PM_SIZE 0x2000
typedef UCHAR IOPM[IOPM_SIZE];
/*
Это будет содержать просто массив нулей, который будет копироваться в настоящую ЮРМ в TSS через Ke386SetIoAccessMap(). Память выделяется во время загрузки драйвера.
*/
IOPM *ю™_1оса1 = 0;
/*
Это две недокументированные функции, которые мы используем, чтобы дать вызы- ваоцему процессу доступ к в/в вызывакщему процессу. Ке386IoSetAccessMap () копирует переданную карту в/в в TSS. Ke386loSetAccessProcess () изменяет указатель смещения 工ОРМ, после чего только что скопированная карта в/в начинает исполь зовать ся. Иначе смещение IOPM указывает за предел сегмента TSS, что приведет к генерации исключения при попытке доступа к в/в любым user-mode-процессом.
*/
void Ke386Set工oAccessMap(int, IOPM *);
void Ke386QueryIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);
/*
Освободить все выделенные ранее объекты
*/
VOID GiveioUnload(IN PDRIVER_OBJECT Driverobject) {
WCHAR DOSNameBuf fer [ ] = L,^\\DosDevices\\,, DEVICE_NAME_STRING;
UN工CODE—STR工NG uniDOSString;
if(IOPM_local)
MmFreeNonCachedMemory(IOPM_local, sizeof(IOPM));
RtllnitUnicodeString(&uniDOSString, DOSNameBuffer);
loDeleteSymbolicLink (&uniDOSString);
loDeleteDevice(DriverObj ect->DeviceObj ect);
/*
Устанавливаем IOPM (карту разрешения в/в) вызывающего процесса так, что ему предоставляется полный доступ к в/в. Наш массив 0РМ_1оса1[] содержит только нули, соответственно IOPM обнулится. Если OnFlag == 1, процессу предоставляется доступ к в/в. Если он равен 0, доступ запрещается.
VOID SetlOPermissionMap(int OnFlag) {
Ke386loSetAccessProcess(PsGetCurrentProcess(), OnFlag);
Ke38 6SetIoAccessMap(1, IOPM_local);
void Give10(void)
{
Se11OPe unis s ionMap(1);
}
/*
Служебный обработчик для user-mode-вызова CreateProcess(). Эта функция введена в таблицу вызовов функций объекта драйвера с помощью DriverEntry(). Когда user-mode-приложение вызывает CreateFile(), эта функция получает управление все еще в контексте вызвавшего приложения, но с CPL (текущий уровень привилегий процессора), установленным в 0. Это позволяет производить операции, возможные только в kernel mode. GiveIO() вызывается для предоставления вызывающему процессу доступа к в/в. Все что приложение режима пользователя, которому нужен доступ к в/в, должно сделать -это открыть данное устройство, используя CreateFile(). Никаких других действий не нужно.
NTSTATUS GiveioCreateDispatch(
IN PDEVICE_OBJECT Deviceobject,
IN PIRP Irp
)
{
GiveIO(); // give the calling process I/O access Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS;
/*
Процедура входа драйвера. Эта процедура вызывается только раз после загрузки драйвера в память. Она выделяет необходимые ресурсы для работы драйвера. В нашем случае она выделяет память для массива IOPM и создает устройство, которое может открыть приложение режима пользователя. Она также создает символичес кую ссылку на драйвер устройства. Это позволяет user-mode-приложению получить доступ к нашему драйверу, используя \\.\giveio нотацию.
*/
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUN工CODE_STR工NG RegistryPath
{
PDEVICE_OBJECT deviceobject;
NTSTATUS status;
WCHAR NameBuf fer [ ] = L,,\\Device\\,* DEVICE_NAME_STRING;
WCHAR DOSNameBuffer[] = L"\\DosDevices\\" DEVICE_NAME_STRING;
UN工CODE_STR工NG uniName String, uni DOS St ring;
// Выделим буфер для локальной 工ОРМ и обнулим его. IOPM_local = MmAllocateNonCachedMemory(sizeof(IOPM)); if(IOPM_local == 0)
return STATUS.工NSUFF工C工ENT_RESOURCES;
RtlZeroMemory(IOPM_local, sizeof(IOPM));
// Инициализируем драйвер устройства и объект
// устройства (device object)
RtllnitUnicodeString(&uniNameString, NameBuffer);
RtllnitUnicodeString(&uniDOSString, DOSNameBuffer);
status = IoCreateDevice(Driverobject, 0, &uniNameString,
FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObj ect);
if (!NT_SUCCESS(status)) return status;
status = loCreateSymbolicLink (&uniDOSString, &uniNameString);
if (!NT_SUCCESS(status)) return status;
// Инициализируем точки входа драйвера в объекте
// драйвера. Все что нам нужно — это операции создания
// (Create) и выгрузки (Unload)
Drive гObj е ct->Ма j огFuncti on[IRP_MJ_CREATE] = GiveioCreateDispatch;
DriverObj ect->DriverUnload = GiveioUnload;
return STATUS_SUCCESS;
Листинг П7.2, Тест драйвера GivelO (Dale Roberts)
/*
TSTIO.EXE — создан Dale Roberts Компиляция: cl -DWIN32 tstio.c
Назначение: Для тестирования драйвера GivelO производим какой-нибудь в/в. Мы обращаемся к внутреннему динамику PC.
*/
#include <stdio.h>
#include <windows.h>
#include <math.h>
#include <coniо.h>
typedef struct { short int pitch;
short int duration;
} NOTE;
/* Таблица нот.*/
NOTE notes[] = {
{14, 500}, {16, 500}, {12, 500}, {0, 500}, {7, 1000}
/*
Установка частоты динамика PC в герцах. Динамик управляется таймером Intel 8253/8254 с портами в/в 0x40-0x43.
void setfreq(int hz)
{
hz = 1193180 / hz; // базовая частота таймера 1,19 MHz
_outp(0x43, ОхЬб); // Выбор таймера 2, операция записи, режим 3.
_outp(0x42, hz); // устанавливаем делитель частоты
_outp(0x42, hz >〉 8); // старший байт делителя }
/*
Спикер управляется через порт 0x61. Установка двух младших битов разрешает канал 2 таймера 8253/8254 и включает динамик.
*/
void playnote(NOTE note) {
_outp(0x61, _inp(0x61) I 0x03); // включаем динамик
setfreq((int)(400 * pow(2, note.pitch / 12.0)));
Sleep(note.duration);
_outp(0x61, _inp(0x61) & ~0x03); // выключаем }
Открытие и закрытие устройства GivelO. Это должно дать нам прямой доступ к в/в. Потом пытаемся проиграть нашу мелодию.
int main ()
int i;
HANDLE h;
h = CreateFile("\\\\,\\givei。”, GENER工C_READ, 0, NULL, 0PEN_EX工ST工NG, FILE_ATTR工BUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_V7kLUE) {
printf(nCouldn1t access Giveio device\nM);
return(-1);
}
CloseHandle(h);
for(i=0; i < sizeof(notes)/sizeof(int); ++i) playnote(notes[i]);
return 0;
Приложение 8
Реализация процедуры преобразования SINGLE-чисел в строку
Здесь мы приведем пример кода, реализующий ASCII-протокол для чисел с плавающей точкой типа single. Код написан на языке С, без применения системных библиотек, и может использоваться для микропроцессоров 8051/8052. Хотя, конечно, можно воспользоваться функцией sprintf, но ее выполнение потребует подключение библиотеки, добавляющей 3——4 Кбайт кода. Для микропроцессоров это совершенно неприемлемый вариант.
ASCII-протокол, реализованный в листинге П8.1, имеет формат:
П знак числа (символ минус или пробел, если знака нет);
6 цифр числа, включая символ десятичного разделителя;
байты завершения посылки (коды 13, 10).
Для сравнения во второй половине кода приводится реализация того же протокола в бинарном виде. Переключение протоколов производится с помощью переменной bProtocolMode. Бинарный протокол обмена имеет вид: □ байт начала посылки (код 36);
口 четыре байта числа с плавающей точкой (от младшего к старшему);
байт конца посылки (код 13);
□ контрольная сумма.
Еще раз заметим, что бинарный протокол требует значительно меньше байтов для передачи той же информации: если строковый протокол требует (1 + 6 + 2) = 9 байт, то бинарный всего (1 + 4 + 1 + 1) = 7 байт. Кроме того, бинарный протокол использует контрольную сумму для повышения достоверности данных. Посылка контрольной суммы для строкового протокола потребовала бы еще 3 байта.
Листинг П8Л. Передача числа с плавающей точкой в ASCII-протоколе
/*
Программа передачи числа с плавающей точкой строкой или байтами (с) Агуров П.В.
*/
/* тип для преобразования SINGLE в байты */ typedef union{
byte Ь[4];
float f;
} UFLOAT;
/* Режимы посылки байта
/*RS_START— обнулить контрольную сумму*/
/*RS_WORK— засчитать в контрольную сумму*/
/*RS_END— ничего не делать с контроль нойсуммой*/
#define RS_START О
#define RS_WORK 1
#define RS_END 2
/* Посылка одного байта火/
/* Параметры:*/
/*mode — режим посылки байта * /
/*Ь— байт для посылки* /
void SendByte(byte mode, byte b){ data uint i;
if (mode == RS_START) { RsCS = 0;
} else {
if (mode == RS_WORK) {
/* Подсчет контрольной суммы — байтовая сумма */
/* с переполнением*/
RsCS += Ь;
TI = о;
i = 0; do { i++; if (i>350) return; } while (TI);
SBUF = b;
i = 0; do { i++; if (i>350) return; } while (!TI);
data single Ves; /* значение веса */
data byteiPoint; /* точность чисел — позиция десятичной точки */
data bool bProtocolMode; /* тип протокола */
/* Реализация протокола */
void SendRS232(){
data byte i;
data long 1;
data byte RSBuf[6];
[1]
f _tmp.f = Ves; /* значение веса */
if (bProtocolMode){
/* ========================== */
/* ASCII-протокол*/
/*знак или пробел*/
/*6 цифр числа*/
/*конец посылки 0D,0А*/
SendByte(RS_WORK, (f _tmp.f < 0)?0x2D:0x20); /* знак "一" или пробел */ [3]
if (f _tmp.f < 0) /* модуль float */
£_七叩.f = -f_tmp. f;
for (i=0; i< iPoint; i++) /* точность */ f_tmp. f = f_tnip. f * 10.0;
for (i= 0; i < 7; i++) /* чистка буфера пробелами */
RSBuf[i] = 0x20;
SendByte(RS_START, SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK ,SendByte(RS_START, SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK ,3636););f_tmp,b[0]); f_tmp,b[l]); f_tmp. b [ 2 ]); f_tnp.b[3]);f_tmp,b[0]); f_tmp,b[l]); f_tmp. b [ 2 ]); f_tnp.b[3]);
SendByte(RS_START, SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK ,
SendByte(RS_START, SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK , SendByte(RS_WORK ,
36
36
);
);
f_tmp,b[0]); f_tmp,b[l]); f_tmp. b [ 2 ]); f_tnp.b[3]);
f_tmp,b[0]); f_tmp,b[l]); f_tmp. b [ 2 ]); f_tnp.b[3]);
for (i= 0; i < 7; i++){
if ((iPoint != i) |I (iPoint == 0)) {
= (unsigned long)f_tmp.f/10;
RSBuf[i] = (byte)((unsigned long)f_tmp.f - (unsigned long)1 10.0); RSBuf[i]+= 0x30; /* переводим из байта в десятичную цифру */ f_tmp.f = 1;
} else {
RSBuf[i] = 0x2E; /* точка */
}
[7]
for (i= 0; i < 7; i++) /* посылаем число SendByte(RS_WORK, RSBuf[6 - i]);
[8]
/* символы конца посылки 0D, 0A*/
SendByte(RS_WORK, 13);
SendByte(RS_WORK, 10);
} else {
/* Бинарный протокол работы : / * начало посыпки '$'
число single
конец посылки LR
,13,RsCS,13,RsCS););SendByte(RS_END SendByte(RS_END }
,13
,RsCS
,13
,RsCS
);
);
Как видно из кода, перевод числа с плавающей точкой, записанного в переменную ves, производится с помощью следующей п оследовател ьноет и
действий (номера пунктов списка отвечают номерам блоков в листинге П8.1):
Переписываем число в буфер ftmp. Специально описанный тип и float позволяет нам получить доступ как к самому числу ftmp.f, так и к байтам ftmp.b [0], ftmp.b [ 1 ], ftinp. b [2 ], ftmp.b [3].
В зависимости от результата выражения (ves < о) посылается либо символ минус (код 0x2D), либо символ пробел (0x20).
Удаляем знак числа (стараемся обойтись без системной функции abs (), требующей подключения библиотеки).
Делаем предварительные операции, необходимые для получения строки из числа; т. к. нам нужно строго ограниченное число знаков после точки, все лишние знаки мы "отрежем".
Чистим буфер передачи пробелами.
Самый сложный цикл, который, собственно, и производит преобразование числа в строку (разбирается ниже).
Передаем байты полученной строки.
Передаем байты завершения посылки.
Разберем цикл преобразования более подробно. В блоке |4| программы, приведенной в листинге П8.1, мы умножили передаваемое число па 10 ровно столько раз, сколько знаков нам нужно получить после запятой. В результате мы нашли число, округлив которое, мы получим исходное число с нужным нам числом знаков точности, но без точки:
3,1415926 с точностью 3 dacm нам целое число 3,141 х 100() = 3141.
Теперь наша задача последовательно "отрезать*' цифры числа. Получить самую последнюю цифру легко — достаточно нацело поделить число ка 10 и взять остаток от деления. Остаток мы найдем, умножив результат на 10 и вычитая это произведение из исходного числа:
3141 / 10 = 314, остаток 3141 — 314 х 10 = 3141 — 3140 = 1.
Теперь в качестве исходного числа мы возьмем 314 и снова произведем те же действия:
314 / 10 = 31, остаток 314 — 31 х К) — 314 — 31() = 4;
31 / 10 = 3, остаток 31 — 3 х 10 = 1;
3/10 дает остаток 3.
Ясно, что проделать эту операцию нам надо столько раз, сколько цифр нам надо получить.
Собирая вычисленные остатки в буфер, мы получаем строковое представление числа. Проверка (iPointoi) позволяет нам поставить десятичную точку на свое законное место.
Индикаторы состояния модема
В табл. П9.1 приводится описание индикаторов модема. Эта информация окажется полезной при написании программы, работающей с модемом или при отладке программ управления линиями модема.
Таблица П9.1. Индикаторы состояния модема
Линия
Описание
АА
Индикатор АА (Auto Answer) загорается, когда модем установлен в режим автоматического ответа на входящий звонок
TR
Индикатор TR (Data Terminal Ready) загорается, когда модем получает сигнал DTR. Драйвер модема устанавливает этот сигнал в состояние ON, когда компьютер готов к приему или передаче данных. Данный индикатор не светится при работе с компьютером Macintosh, который использует сигнал DTR для другой цели
ОН
Индикатор ОН (Off Hook) светится, когда модем "поднимает трубку" т. е. подключается к телефонной линии. (Он может мигать во время импульсного набора номера.)
CD
Индикатор CD (Carrier Detect) светится, когда модем обнаруживает несущую удаленного модема
HS
Индикатор HS (High Speed) загорается при работе модема с "высокой скоростью". В моделях IDC-1414 и IDC-1914 это означает любую скорость, превышающую 1200 бод
TD(SD)
Индикатор TD (Transmit Data) мигает всякий раз, когда происходит передача данных от терминала к модему. При непрерывной передаче данных на высоких скоростях этот индикатор может светиться почти постоянно
RD
Индикатор RD (Receive Data) мигает, когда модем передает данные терминалу. При непрерывной передаче данных на высоких скоростях этот индикатор может светиться почти постоянно
MR
Индикатор MR (Modem Ready) светится постоянно, если модем исправен и включен в сеть. Индикатор может мигать, когда модем выполняет самотестирование
Инструменты
При написании книги мы попробовали множество программ для работы с пос л ед о вате л ьн ы м и портами, часть из которых представляем вашему вниманию.
Порт-монитор
Лучшей программой мониторинга обращений к порту мы считаем программу Марка Руссановича (Mark Russinovich) Portmon (рис. П10.1). На его сайте http://www.sysinternals.com свободно доступны две версии программы: для Windows 98 и для Windows NT/2000/XP.
Программа Portmon позволяет отслеживать обращения к последовательному порту как локальной, так и удаленной машины, отображать исполняемые команды и время их выполнения. Это действительно ценный инструмент!
Рис. П10.1. ПрограммаPortmon Марка Руссановича
Разработка драйверов Driver Wizard
Довольно интересная программа (рис. П10.2). Ее отличие от остальных заключается в возможности генерации кода для Delphi. Естественно, Delphi- код работает с помощью специального VxD-драйвера. Сайт разработчиков —— http://www.jungo.com.
EdftPOEdftPOFor HeS pres^ F1For HeS pres^ F1CancelCancelGer变沁旅 亦I中径Ger变沁旅 亦I中径Ger侬观 .IMF fifeGer侬观 .IMF fifeOther hardwaieParallel portPCI VIA VT8363 Host Bus-PCI EfidgePCI. VIA VIB363 PCI to AGP BddgePCI: VIA VT82C686 Pa^to^SA btidgePCI: VIA VTB2C5B6/686 PCI IDE ConhoHerPCI: VIA VT83C572PCI USB ContiollerPCI: VIA VT83C572 PCI USB ContfollerPCI: VIA VTB2C686A ACPI Power Mariagernent ControilerPCI: Matrox MGA-2064W Milieniuni GUI AcceleratorPCI: AT&T 1646T00 V.90 Lucent ModemPCI. С-Media CMI8738/C3DX PCI Audio DeviceISA PnP- No caids foundPCMCIA: No cards foundUSB. VendorlD OeaO . PioductlD 6803Other hardwaieParallel portPCI VIA VT8363 Host Bus-PCI EfidgePCI. VIA VIB363 PCI to AGP BddgePCI: VIA VT82C686 Pa^to^SA btidgePCI: VIA VTB2C5B6/686 PCI IDE ConhoHerPCI: VIA VT83C572PCI USB ContiollerPCI: VIA VT83C572 PCI USB ContfollerPCI: VIA VTB2C686A ACPI Power Mariagernent ControilerPCI: Matrox MGA-2064W Milieniuni GUI AcceleratorPCI: AT&T 1646T00 V.90 Lucent ModemPCI. С-Media CMI8738/C3DX PCI Audio DeviceISA PnP- No caids foundPCMCIA: No cards foundUSB. VendorlD OeaO . PioductlD 6803card descriplicai:Choose tNs for ISA drivel developmentcard descriplicai:Choose tNs for ISA drivel developmentiSACard:iSACard:
EdftPO
EdftPO
For HeS pres^ F1
For HeS pres^ F1
Cancel
Cancel
Ger变沁旅 亦I中径
Ger变沁旅 亦I中径
Ger侬观 .IMF fife
Ger侬观 .IMF fife
Other hardwaie
Parallel port
PCI VIA VT8363 Host Bus-PCI Efidge
PCI. VIA VIB363 PCI to AGP Bddge
PCI: VIA VT82C686 Pa^to^SA btidge
PCI: VIA VTB2C5B6/686 PCI IDE ConhoHer
PCI: VIA VT83C572PCI USB Contioller
PCI: VIA VT83C572 PCI USB Contfoller
PCI: VIA VTB2C686A ACPI Power Mariagernent Controiler
PCI: Matrox MGA-2064W Milieniuni GUI Accelerator
PCI: AT&T 1646T00 V.90 Lucent Modem
PCI. С-Media CMI8738/C3DX PCI Audio Device
ISA PnP- No caids found
PCMCIA: No cards found
USB. VendorlD OeaO . PioductlD 6803
Other hardwaie
Parallel port
PCI VIA VT8363 Host Bus-PCI Efidge
PCI. VIA VIB363 PCI to AGP Bddge
PCI: VIA VT82C686 Pa^to^SA btidge
PCI: VIA VTB2C5B6/686 PCI IDE ConhoHer
PCI: VIA VT83C572PCI USB Contioller
PCI: VIA VT83C572 PCI USB Contfoller
PCI: VIA VTB2C686A ACPI Power Mariagernent Controiler
PCI: Matrox MGA-2064W Milieniuni GUI Accelerator
PCI: AT&T 1646T00 V.90 Lucent Modem
PCI. С-Media CMI8738/C3DX PCI Audio Device
ISA PnP- No caids found
PCMCIA: No cards found
USB. VendorlD OeaO . PioductlD 6803
card descriplicai:
Choose tNs for ISA drivel development
card descriplicai:
Choose tNs for ISA drivel development
iSACard:
iSACard:
Рис. П10.2. Программа Driver Wizard
Разработка драйверов
NuMega Driver Studio
Специальный модуль, подключающийся к MS Visual Studio. Позволяет создать драйвер, поеледовательно отвечая на вопросы помощника. Пожалуй, лучшая программа для разработки драйверов. Сайт — http://www.numega.com.
Рис. П10.3. Программа NuMega Driver Studio
Отладчик NuMega SoftICE
Мы думаем, эта программа не нуждается в дополнительном представлении. Можно сказать, что этот отладчик драйверов, н вообще любых программ, вне конкуренции.
Эмулятор компьютера
Connectix Virtual PC
Полноценный эмулятор персонального компьютера (рис. П Ю.4). Позволяет тонко настроить ресурсы эмулируемого компьютера, например, СОМ-порт можно настроить на ввод/вывод из файла. Более того, можно связать две системы, запущенные в эмуляторе по коммуникационному порту.
Терминал HyperTerminal
Небольшой терминал, встроенный в Windows (рис. П Ю.5). Довольно неудобно производится ко 11 фи гур 11 ро ва 11 и е настроек соединен ия, не запоминает имена передаваемых файлов, но пользоваться можно.
К
Рис. П10.4. Программа Virtual PC
Win98
Not Running
Рис. П10.5. Программа HyperTerminal
Приложение 11
Формат команды Mode
Для установки параметров коммуникационного порта, например СОМ1 или COM2, используется команда следующего формата:
mode comm[:] [baud=b] [parity=p] [data=d] [stop=s] [to=onI off] [xon=onI off] [odsr=onI offl [octs=on(off] [dtr=on|off|hs] [rts=onI off IhsItg] [idsr=onI off]
Параметры этой команды следующие.
сомш задает номер порта, например СОМ1 или COM2.
baud = ъ. Может устанавливаться скорость 110, 150, 300, 600, 1200, 2400, 4800, 9600 или 19200 бит/с. Указывать нужно только 2 цифры, т. е. baud=i9 будет означать выбор скорости 19200.
П parity = р. Задает режим контроля четности. Параметр р может принимать следующие значения:
п (нет контроля четности);
о (нечетный);
е (четный, по умолчанию);
m (фиксированная четность MARK):
s (фиксированная четность SPACE).
□ data = d. Указывает число битов в байте. Параметр d может принимать значения от 5 до 8 (по умолчанию 7). Не все компьютеры поддерживают значения 5 и 6.
口 stop = s. Определяет число стоп-битов. Параметр з может принимать значения:
1 (по умолчанию);
1,5;
2 (по умолчанию при скорости 110).
to = on I off. Устанавливает бесконечный тайм-аут (если on).
口 xon = on I off. Включает использование протокола xon/xoff.
口 odsr = on I off. Включает режим DSR-квитирования (handshaking DSR).
口 octs = on I off. Включает режим CTS-квитирования (handshaking CTS).
口 dtr = on I off I hs. Управляет линией DTR, включая ее (on), выключая (off) или устанавливая в режим квитирования (hs).
口 rts = on I off I hs. Управляет линией RTS, включая ее (on), выключая (off) или устанавливая в режим квитирования (hs).
口 idsr = on I off. Управляет линией DSR.
Настройки порта сохраняются только в текущей сессии Windows, т. е. после перезагрузки будут снова установлены параметры по умолчанию:
口 скорость 1200;
контроль четности выключен;
口 число битов данных 7;
число стоп-битов 1.
То же самое происходит при любых изменениях в Менеджере устройств (Device Manager). Для исправления первой ситуации рекомендуется создать простой ярлык с параметрами Минимизировать при запуске (Minimized) и Закрывать после выполнения (Close window on exit), содержащий строку изменения параметров, например:
С: \winnt\system32\mode. com coml: 9600, п, 8,1
Настройки порта принято записывать в сокращенной форме, например:
8N1 означает: 8 битов данных, нет четности, 1 стоп-бит;
7Е1 означает: 7 битов данных, четная (Even) четность, 1 стоп-бит.
Приложение 12
Часто задаваемые вопросы (FAQ)
FAQ: Общие вопросы
Что такое СОМ и RS и чем они отличаются?
Ответ: СОМ ——сокращение от слова Communication ——последовательный. COM-порт означает поеледовательный порт. RS ——название стандарта поел едовател ьного обмена. Настоящее название стандарта ''EIA/TIA-232-E", а аббревиатура “RS” расшифровывается как *'Recommended Standard1'. Подробности описаны в а?. 7.
Что такое порт?
Ответ: слово "порт” применяется в разных смыслах. Во-первых, это может быть просто разъем компьютера, например — ''подключить к СОМ-порту нуль-модемный кабель". Во-вторых, это может быть некий виртуальный адрес, записывая или считывая значение которого, можно получать определенную информанию. Эти порты адресуются с помощью операторов Port и Portw. Например、“записать в порт $3FB число $80”.
Чем "протокол RS-232"
отличается от "интерфейса RS-232"?
Ответ: так же как с понятием "порт”,тут происходит некоторая путаница. Для связи двух устройств с помощью последовательной передачи данных используются две составляющие: аппаратная и программная. Аппаратная составляющая описывается стандартом RS-232 (или родственными стандартами типа RS-485, RS-422): уровни напряжений, разъемы, назначение сигналов и т. п. Программная реализация зависит от конкретного устройства, но условно ее тоже можно поделить на две части: системную (установка параметров связи, инициализация и т. д.) и пользовательскую (последовательность передачи данных). Часто системную часть называют протоколом RS-232.
Что такое контрольная сумма и как ее считать?
Ответ: контрольная сумма ——дополнительные байты, добавляемые к пакету данных, позволяющие клиенту получить ивформацию о достоверности данных. Чаще всего это просто сумма всех байтов данных. Подробности см. в раз。. 24, гл. 14 и 75.
Откуда взялось ограничение числа абонентов в протоколе RS-485?
Ответ: ^Допустимая нагрузка драйвера RS-485/RS-422 количественно определяется в терминах единичной нагрузки, которая, в свою очередь, определяется как входной импеданс одного стандартного приемника RS-485 (12 кОм). Таким образом, стандартны!! драйвер RS-485 может управлять 32 единичными нагрузками (32 пapaллeJIЫlЫx 12-килоомных нагрузки). Однако для некоторых приемников RS-485 входное сопротивление является более высоким — 48 кОм (1/4 единичной нагрузки) нлн даже 96 кОм (1/8 единичной нагрузки) — и, соответственно, к одной шине могут быть подключены сразу 128 или 256 таких приемников. Вы можете подключить любую комбинацию типов приемников, если их параллельны ri импеданс не превышает 32 единичных нагрузки (т. с. суммарное сопротивление не меньше 375 Ом)” |3].
Почему нужно подключать СОМ-устройства при выключенном питании?
Ответ: естественно, подключение устройств при выключенном питании касается только устройств с автономным питанием. Для подючючения устройств типа мыши выключение питания не лребуется (другое дело, что Windows или драйвер DOS может ее опознать только после перезагрузки). А вот устройства с независимым питанием могут создать разность потенциалов, которая в момент подключения может оказаться приложенной к выходным или, еще хуже, входным цепям интерфейса и вывести из строя микросхемы UART. На современных материнских платах микросхемы устанавливаются без панелек, и для замены их нужно выпаивать из платы. Такая операция возможна далеко нс всегда.
Откуда берется питание для мыши?
Закономерный вопрос. Ответ: стандарт последовательной связи никак не определяет питание устройств. Для мыши вопрос решен просто: питание берется от неиспользуемых сигнальных цепей, установленных в состояние 1.
Некоторые мыши переключают режим работы в зависимости от установленных питающих сигналов.
Что означает режим HANDSHAKING?
Ответ: handshaking (квитирование установления связи) — это метод, с помощью которого компьютеры и другие устройства обмениваются статусной информацией. В стандарте RS-232, сигналы квитирования (handshaking signals) используются одним из связанных устройств для того, чтобы сообщить другому о своей готовности к приему данных. Аппаратное квитирование —когда эти сигналы передаются по специальным каналам (handshaking wires). Программное квитирование — когда статусная информация передается через специальные информационные байты в общем потоке данных.
Что такое дуплексная и полудуплексная передача?
Ответ: полный дуплекс (Full Duplex) подразумевает, что компьютер может одновременно передавать и принимать данные — существует два раздельных канала данных (один для входящих данных и один для исходящих).
Полудуплекс (Half Duplex) означает, что компьютер не может одновременно передавать и принимать данные. Обычно это подразумевает, что существует только один канал передачи данных. Однако это не значит, что любой из сигналов RS-232 не используется. Скорее всего, коммуникационная линия использует какой-нибудь другой стандарт, отличный от RS-232, который не поддерживает полнодуплексную работу.
FAQ: Программирование в DOS
Что такое базовый адрес порта?
Ответ: базовый адрес порта ——2-байтовое число, относительно которого считаются все остальные порты поеледовательной связи. Например, базовый адрес СОМ1 равен $3FB, соответственно адрес порта IER будет $3FB + I = S3FC.
Написал программу обработки прерываний порта, а она периодически "виснет".
Для других прерываний все нормально работает
Ответ: скорее всего, вы забыли послать сигнал eoi контроллеру прерыва- ний. Прерывания последовательного порта представляют собой аппаратные
прерывания (IRQ). Для их корректной обработки применяются специальные методы, описанные в гл. 8.
При выводе на экран данных, полученных в обработчике прерывания, выводится "мусор"
Ответ: вывод на экран использует прерывание INT21H для вывода символа, позиционирования курсора и т. д. Использование прерываний внутри аппаратного прерывания не очень корректно. Кроме того, прерывания последовательного порта являются аппаратными прерываниями и имеют наивысший приоритет. Длительная задержка внутри обработчика прерывания может привести к сбою системы. Мы рекомендуем убрать отображение данных из обработчика прерываний, а в обработчике только лишь проверять их достоверность и складывать в буфер. Печать и использование данных лучше делать в основном цикле программы.
Прерывание и основная программа используют один буфер.
Как их синхронизировать в DOS?
Ответ: конечно, в DOS нет методов синхронизации, принятых в Windows. Поэтому перед выборкой данных из буфера нужно запретить прерывания, быстро скопировать данные в локальный буфер и снова разрешить прерывания. Это позволит избежать конфликтов, если основная программа начала забирать данные из буфера, забрала их до середины, а в это время обработчик прерывания их поменял. При этом следует запретить не только маскируемые, но и аппаратные прерывания соответствующего IRQ. См. разд. 2.4 и гл. 8.
Как бы перехватить вывод программы в СОМ-порт под DOS?
Ответ: если программа использует INT14H или INT21H, то достаточно просто перехватить обработчик прерывания. Но, вообще говоря, на это лучше не рассчитывать. Ограниченность сервисов BIOS приводит к необходимости программирования портов напрямую, без использования системных функций. В этом случае проще всего воспользоваться эмулятором PC, например VmWare. Хороший эмулятор умеет перенаправлять вывод в файл или пайп. Найти VmWare можно на сайте http://www.vmware.com.
FAQ: Программирование в Windows
Почему в Delphi пропал оператор Port и как тогда обращаться к портам?
Ответ: в Delphi 1 этот оператор еще присутствовал, а начиная с версии 3 — удален. Дело в том, что Windows не позволяет прямого обращения к портам. Заменой может служить класс TPort, описанный в гл. 9.
Можно ли использовать прямое обращение к портам в Windows NT/2000?
Ответ: нет. Для доступа к портам нужен специальный VxD-драйвер. Для Delphi существует модуль TGiveio, подключение которого делает возможным прямое обращение к портам. Этот модуль содержит в себе бинарный образ VxD-драй вера и загружает его в память при запуске программы. См. разд. 9.3
Программа асинхронно работает с портом. В Windows 98 все работало, а в Windows 2000 или "виснет", или не работает
Ответ: проверьте, что порт открыт с помощью флага file_flag_overlapped. Windows 98 терпимо относится к смешиванию синхронного открытия и асинхронных операций, и наоборот. А вот Windows 2000 такого произвола не допускает. Убедитесь, что дескриптор асинхронной структуры overlapped создан и используется корректно.
Зачем надо использовать WaitForSingleObject, если уже есть вызов GetOverlappedResult?
Ответ: вызов GetOveriappedResuit действительно дождется завершения асинхронной операции, но прервать это ожидание или задать время ожидания невозможно. Вызов waitsingleobject позволяет более гибко управлять ожиданием завершения. А использование waitForMuitipleObjects позволяет вводить дополнительные сигналы завершения ожидания.
Как найти все доступные СОМ-порты?
Ответ: во многих источниках приводится код перебора имен портов от 1 до 32 и попытки их открытия. Так делать не рекомендуется. Во-первых, если
порт занят, он не будет распознан, а во-вторых, максимальное число портов зависит от версии Windows. Для перебора портов используйте системную функцию EnumPorts, применение которой показано в 乙乙 13.
Как бы перехватить вывод программы в COM-порт под Windows?
Ответ: проще всего воепользоваться эмулятором PC ——см. ответ № 5 из FAQ для DOS. Иначе придется написать свой драйвер COM-порта, например, с помощью Nuniega Driver Studio или Jungo Windriver.
Если нужно перехватить не вывод программа: а команды, передаваемые программой (т. е. открытие/закрытие порта, программирование режимов порта и т. д.), то можно воспользоваться утилитой Poll Мои, созданной Марком Руссоновичем (Mark Russinovich) с его сайта http://www.sysinternals.com,
Как выключить Plug and Play для COM-порта при загрузке Windows?
Ответ: если СОМ-портов на вашем компьютере нет, или к ним подключено нестандартное оборудование, стоит отключить автоопределение устройств, чтобы "не смущать*' операционную систему.
В Windows 2000/ХР можно указать параметр /fastdetect = С0М1,2,3
в файле загрузки Boot.ini, указывающий Windows пропустить поиск устройств на заданных портах. Если номера ис указаны, поиск отключается на всех СОМ-портах.
В Windows NT этот параметр назывался /noserialmice.
FAQ: Связь, модемы, терминалы
Что такое FIFO и FOSSIL?
Ответ: аппаратно реализованная очередь (FIFO, First In First Out) в аппаратной части поел ед овател ьн о го порта. Нормально реализованным FIFO считается UART 16550А, 16550AF или 16550AFN. В некоторых моделях внутренних (internal) модемов аппаратно установлена микросхема UART 16550AFN. A FOSSIL — это драйвер COM-порта, ориентированный на работу с модемом, подключенным к COM-порту. Существует много разнообразных версий и типов FOSSIL-драйверов. Наибольилее распространение получили BNU и ХОО.
Драйвер FOSSIL (сокращение от Fido/Opus/Seadog/Standart/Layer) занимается в основном буферизацией потоков из (от) модема. Он перехватывает аппаратное прерывание от COM-порта, которое возникает по приходу (уходу) каждого байта или возникновению ошибок. На большой скорости обмена байты приходят быстро, и, соответственно, прерывание вызывается часто и времени на считывание пришедшего байта из регистров UART остается мало. Драйвер может и не успеть считать байт, и он "потеряется**, а это, в свою очередь, приведет к ошибке контрольной суммы (CRC Error) в целом блоке данных.
FIFO позволяет организовать все это несколько иначе — приходящие байты будут копиться во внутреннем буфере (обычно до 14—16 байт). При заполнении буфера генерируется прерывание, и драйвер FOSSIL считывает весь буфер целиком за один раз. При этом время реакции драйвера FOSSIL на прерывание уже не так критично ——приходящие байты будут складироваться в тот же внутренний буфер.
Описание компакт-диска
Папки
ОписаниеРазделы
\GLAVA06\2
Программа приема и передачи данных с использовани- 6.2 ем сервиса BIOS INT14H
\GLAVA07\2
Определение наличия COM-портов и их базовых адре-7.2
сов в DOS
\GLAVA07\3-4
Прямое программирование коммуникационных портов 7.3, 7.4 в DOS
\GLAVA08\2-3
Прием и передача данных с помощью прерываний8.2, 8.3
коммуникационного порта в DOS
\GLAVA09\1
Класс TPort для обращения к портам из Windows9.1
\GLAVA09\2
Пример использования класса TPort для обращения9.2
к портам из Windows
\GLAVA09\3
Драйвер GivelOEx для прямого обращения к портам9.3
из Windows NT/2000/XP и пример его использования.
Для компиляции драйвера необходимо наличие
Windows 2000 DDK
\GLAVA10\4
Пример последовательной связи с использованием10.4
функций Windows
\GLAVA11\2
Пример использования потоков для последовательной11.2
связи в Windows
\GLAVA12\3
Пример последовательной связи из Windows с помощью 12.3 асинхронных функций
\GLAVA13\1
Дополнительные коммуникационные функции: управле- 13.1 ние линиями DTR/RTS и определение базового адреса порта (Windows 98)
\GLAVA13\2
Прямое управление драйвером коммуникационного13.2
порта с помощью функции DeviceloControl
\GLAVA13\3
Обнаружение всех последовательных портов в системе 13.3
(окончание)
Папки
Описание
Разделы
\GLAVA13\4
Работа с именами портов (Windows NT/2000/XP)
13.4
\GLAVA13\5
Использование АРС-функций Windows 2000
13.5
\GLAVA14\1-2
Прием и передача простого пакета данных (строка).
Реализация компонента TComPort и редактора свойств
14.1,
14.2
\GLAVA14\3
Реализация ASCII-протокола
14.3
\GLAVA14\4
Реализация бинарного протокола
14.4
\GLAVA16\2
Тестирование свойств СОМ-порта
16.2
\GLAVA16\3
Реализация протокола RS-485
16.3
\GLAVA17\1
Эмулятор РпР-устромства, INF-файл для установки драйвера
17.1
\GLAVA17\3
Обнаружение изменений в аппаратной конфигурации системы
17.3
Литература
и интернет-ресурсы
Perrin В. The Art and Science of RS-485.— http://www.gdscorp.com/pdf/rs485.pdf.
Maxim's Application Note #083, 119, 373, 723, 367.— http:/ /www.maxim-ic.com.
Microsoft Development Network. — http://msdn.microsoft.com/.
Бень E. A. RS-485 для чайников.—
http://kilm.by.ru/ini/inter/rs485/chaiiiik.slitml.
Гук М. Аппаратные средства IBM PC. Энциклопедия. — СПб.: Питер, 2002.
Гук М. Аппаратные интерфейсы IBM PC. Энциюгопедия. — СПб.: Питер, 2002.
Конопка Р. Создание оригинальных компонент в среде Delphi. — Киев: DiaSoft Ltd., 1996.
Лагутенко О.И. Модемы. Справочник пользователя. —- СПб.: Лань. 1997.
Локотков А. Интерфейсы последовательной передачи данных. Стандарты RS-422/RS-485. В записную книжку пнженера//СТА. — 1997 ~ № 3.— С. 110-119.
Лукач Ю. С. Программно-технические средства персональных ЭВМ семейства IBM PC. — Свердловск: Инженерно-техническое бюро, 1990.
Рихтер Д. Windows для профессионшюв. — Microsoft Press, 1999.
Робертс Д. Прямой INPUT/OUTPUT в среде WINNT.- http://www.void.ru/content/701 /.
Предметный указатель
А
INTI4H 100, 311
INT21H 316
АРС 231 ARQ41
【ОРМ 165
С
Р
COM-порт 11
Plug and Play:
алгоритм для СОМ-портов 73
D
вычисление поля PnP Rev 292 запуск процедуры 67
DCE 12
DTE 12
иде нтифл катор 78
обнаружение изменений 303 реестр Windows 2000 71
I
формирование идентификатора 293 функции 66
INF-файл:
эмулятор 293
ключевые поля 301
поиск при установке 300
и
INF-файл:
структура 84
UATR 11
А
CTS 325
DCD 325
Активная пауза 45
Б
DCTS 325
DDCD 325
DDSR 325
DLAB 1()6, 320. 322
Базовый адрес:
значение 104
определение в DOS 105
определение в Windows 98 202 таблица 319
Бит:
BD 324
BRCON 322
DR 324
DSR 325
DTRC 324
EVEN PAR 322
FE 324
FI FOE 324
IE 323
LME 323
Low Power Mode 320
ОЕ 324
OUTIC 323
PAREN 323
РЕ 324
RI 325
RTSC 323
SERIALDB 323
Sleep Mode 320
STI PAR 322
STOPB 323
TEMPT 324
TERI 325
THRE 324
Битстаффин гован ие 44
CLRDTR 201
FI LE_FLAG„O VERLAPPED
194, 231
SERIAL_xxx 402, 403
SETBREAK 201
SETDTR 201
Контроль четности 4| недостатки 184
Контрольная сумма:
CRCI6 (алгоритм) 43
CRC16 (вычисление) 275
CRC32 (вычисление) 279
LCR (вычисление) 274
LRC (алгоритм) 42
1ШДЫ 42
д
Дескриптор порта 166
Драйвер Givelo:
простая (алгоритм) 42
простая (вычисление) 269, 273
м
использование 144
недостатки 165
описание 143
расширение возможностей 148
Модем:
АТ-команды 49, 409
Escape-последовательность 50
и
регистры 50
функции 49
Инициализация порта:
INTI4H 100, 312
прямое программирование 106
к
Модуль:
RS232DOS 111
RS2321nt 115
н
Класс:
ТСоп1485 288
TComAscii 254
TComBin 263
Нуль-модемный кабель 12
о
TComPort 168
Обратная связь 40
TComPort асинхронный 195
TComPort компонент 241
TComPort с потоками 186
TComPortOnline 207
TConiProp 241
TPort 130
TReadThread 192
Константа:
CBR—Xxx 168
CLRBREAK 201
п
Порт:
DOS-имя 225
FCR 403
IER 113, 320
IIR 321
LCR 322, 398
(окончание рубрики см. 〃〃 с. 474)
Порт (окончание):
LSR 324
MCR 168. 323
MSR 325
RBR 320
TSR 320
завершения аппаратных прерываний 327
обнаружение 222
обращение из Windows 127
получение доступа из Windows 143 разрешение аппаратных прерываний 326
режимов FIFO 321
таблица IRQ 319
Последовательный интерфейс:
принцип работы 19
разъемы 20
события в Windows 197
Потоки:
использование для чтения порта 185
преимущества 185
синхронизация 192
Прерывания:
обработка 114
типы 113
Протокол:
3D Serial Mouse 47
ASCII 33, 254
Bi modem 55
CAN 61
HDLC 44
Kermit 55
M icrosoft Mouse 46
Microsoft Plus 47
MODBUS 45, 56
MODBUS-ASCII 57
MODBUS-RTU 58
PC Mouse 48
Profibus 64
RS-48 5 288
SLIP 45
XModem 51
XModem-1К 52
XMoclem-CRC 52
YModem 53
YModem-G 54
Z Mode in 54 бинарный 35. 263 модем ASCII 50 потеря данных 35 составляющие 12, 32 типы 33
Прямое управление драйвером 203
р
Разрыв связи 362
с
Сигншг
OUT1 323, 402
OUT2 402
готовность DCE (CC/DSR) 25, 325 готовность DTE (CD/DTR) 25, 201, 399, 401. 402
готовность к передаче (CB/CTS) 24, 325
готовность к передаче по дополнительному каналу (SCB/SCTS) 29
готовность к приему (CJ) 26 детектор качества сигнала
(CG/SQ) 26 запрос передачи (CA/RTS) 23, 399, 400, 401, 402
запрос передачи по дополнительному каналу (SCA/SRTS) 29
защитная "земля" (АА) 22 индикатор вызова (CE/RI) 25. 325 индикатор тестирования (ТМ) 27 местный шлейф (LL) 27 обнаружение несущей (CF/DCD) 25, 325
обнаружение несущей до п ол н ител ьн о го канала (SCF/SDCD) 29
передаваемые данные (BA/TxD/TD) 23
передаваемые данные дополнительного канала (SBA/STD) 28
переключатель скорости передачи данных от DCE (С1) 26
переключатель скорости передачи данных от DTE (СН) 26
принимаемые данные (BB/RxD/RD) 23
принимаемые данные дополнительного канала (SBB/SRD) 28
сигнальная ''земля'* (AB/SG) 22
синхронизация передачи от DCE (DB/TC) 28
синхронизация передачи от DTE (DA) 28
синхронизация приема от DCE
(DD/RC) 28
удаленный шлейф (RL) 27
Синхронизация:
в DOS 38
в Windows 38, 192
Скорость обмена:
вычисление 108, 325
задание в DOS 106, 320
задание в Windows 342
максимально допустимая 332
Стандарт:
RS-232 14
RS-232 недостатки 282
RS-422A 15
RS-423A 15
RS-449 16
RS-485 15, 282, 288
RS-562 16
.24 17
.28 17
.35 17
.2 Ibis 18
двойные названия 12
.21 18
Стартовый байт 44
Структура:
COMMCONFIG 167, 328, 329, 372, 382
СОММ PROP 168, 330
COMMTIMEOUTS 167, 336,
361, 377
COM STAT 339, 364, 382
DC В 167. 201. 340. 359. 361. 376
MODEMDEVCAPS 335
OVERLAPPED 351
TSER1AL_CHARS 401, 405
TSER1AL_COMMPROP 402, 405
TSER1AL_HANDFLOW 402, 405
TSER1AL_L1NE_CONTROL 398, 401. 404
TSER1AL_QUEUE_S1ZE 404
TSER1AL_STATUS 402, 404
TSER1AL_T1MEOUTS 399, 4U4
TSERIAL—XOFF—COUNTER 405
TSER1ALPERF_STATS 403, 406
TWMDeviceCliange 303
Ф
Функция:
BuildCommDCB 167, 359, 361
BuildConimDCBAndTiineouts
361
Cancello 364
ClearCommBreak 201, 362, 399
ClearCommEiTor 340, 351, 363, 370
Close Handle 166, 347
CommConfigDialog 167, 241, 329, 374, 381
Create Event 367, 369
Create File 166. 347
Defl ne Dos Device 226, 395
DeviceloControl 148, 203, 388, 397
Enum Ports 222, 390
EscapeCommFunotion 201, 365,
399, 400
GetCommConflg 167, 329, 372
Get Comm Mask 366、400
GetComniMudemStatus 168. 369.
383, 402
Get Comm Prop 402
GetCommProperties 168, 330, 375
Get Comm State 360, 376. 401, 402
GetCommTimeouts 336, 377, 399
Get Default CommConflg 167,
374, 381
GetOveiiapped Result 194, 351,
369, 387
(окончание рубрики см. на с. 476)
Функция (окончание)'.
PurgeComm 378, 401
QueryDosDevice 226, 393
ReadFile 166, 】94, 350, 370
ReadFileEx 166, 231, 351, 3 54
SetCommBreak 201, 362, 399
SetCommConfig 167, 329, 372
SetCommMask 198, 366, 368, 401
SetConimState 167, 360, 376, 398, 399, 401, 402
SetCommTimeouts 167, 336, 351, 377, 399
SetDefaultCommConfig 167, 374, 381
SetupComm 168, 380, 398
SleepEx 231
Synchronize 192
TransmitCommChar 382, 399
WaitCommEvent 198, 366. 368, 401
WaitForMultipleObjects 194, 385
WaitForMultipleObjectsEx 231
WaitForSingleObject 194, 198,
369, 384
WaitForSingleObjectEx 231
Windows для работы с портами 166
WriteFile 166, 194, 352
WriteFileEx 166, 231, 353, 357
Павел Агуров
Последовательные интерфейсы ПК
Практика программирования
Санкт-Петербург«БХВ-Петербург»2004
Агуров П. В.
Последовательные интерфейсы ПК. Практика программирования.—— СПб.: БХВ-Петербург, 2004. — 496 с.: ил.
Книга представляет собой практическое руководство по программированию последовательных интерфейсов. В первой части книги представлены теоретические сведения о последовательных интерфейсах, во второй — практические примеры и листинги программ на языках Pascal и Delphi, а третья содержит справочные данные, облегчающие поиск необходимой информации. В приложениях приведены дополнитсльные сведения и ответы на часто задаваемые вопросы. Большое количество практических советов, примеров программ, а также последовательность и простота изложения позволят читателю уверенно овладеть изложенным в книге материалом. Для удобства читателей все исходные коды приводятся на прилагаемом к книге ко мпакт-дис ке.
Для программистов, занятых в сфере промышленной автоматизации
Группа подготовки издания:
Главный редакторЗам. главного редактора Зав. редакциейРедакторКомпьютерная верстка КорректорОформление серии Дизайн обложки Зав. производствомГлавный редакторЗам. главного редактора Зав. редакциейРедакторКомпьютерная верстка КорректорОформление серии Дизайн обложки Зав. производствомЕкатерина Кондукова Евгении Рыбаков Григорий Добин Алексей Семенов Натальи Смирновой Наталия Першакова Via Design Игоря Цырулышкова Николай ТверскихЕкатерина Кондукова Евгении Рыбаков Григорий Добин Алексей Семенов Натальи Смирновой Наталия Першакова Via Design Игоря Цырулышкова Николай Тверских
Главный редактор
Зам. главного редактора Зав. редакцией
Редактор
Компьютерная верстка Корректор
Оформление серии Дизайн обложки Зав. производством
Главный редактор
Зам. главного редактора Зав. редакцией
Редактор
Компьютерная верстка Корректор
Оформление серии Дизайн обложки Зав. производством
Екатерина Кондукова Евгении Рыбаков Григорий Добин Алексей Семенов Натальи Смирновой Наталия Першакова Via Design Игоря Цырулышкова Николай Тверских
Екатерина Кондукова Евгении Рыбаков Григорий Добин Алексей Семенов Натальи Смирновой Наталия Першакова Via Design Игоря Цырулышкова Николай Тверских
© Агуров П. В.. 2004
© Оформление, издательство "БХВ-IIетербург'. 2004
Содержание
Введение
Для кого эта книга1
Структура книги2
Краткое описание глав 3
Программные требования6
О программном коде 6
Обозначения7
Аппаратные требования7
Благодарности7
Обратная связь7
Часть I. Протоколы и интерфейсы9
Глава 1. Стандарты последовательной связи11
Стандарты последовательной связи11
ПротоколRS-232 13
ПротоколRS-422A15
ПротоколRS-423A15
ПротоколRS-485 15
ПротоколRS-49916
ПротоколRS-56216
ПротоколV.2417
ПротоколV.2817
ПротоколV.35 17
Протокол Х.21 18
Рекомендация X.21bis 18
Краткое сравнение RS-протоколов 18
Принципы последовательной связи 19
Разъемы коммуникационного порта20
Сигнальная "земля" (AB/SG)22
Защитная "земля" (АА)22
L3.3. Передаваемые данные (BA/TxD/TD)23
Принимаемые данные (BB/RxD/RD)23
Запрос передачи (CA/RTS)23
Готовностьк передаче (CB/CTS)24
ГотовностьDCE (CC/DSR)25
ГотовностьDTE (CD/DTR)25
Индикаторвызова (CE/RI)25
Обнаружение несущей (CF/DCD)25
Детектор качества сигнала (CG/SQ)26
Переключатель скорости передачи данных от DTE (СН)26
Переключатель скорости передачи данных от DCE (CI)26
Готовность к приему (CJ)26
Местный шлейф (LL)27
Удаленный шлейф (RL)27
Индикатор тестирования (ТМ)27
Синхронизация передачи от DTE (DA)28
Синхронизация передачи от DCE (DB/TC)28
Синхронизация приема от DCE (DD/RC)28
Передаваемые данные доп ол н ител ьно го канала (SBA/STD)28
Принимаемые данные дополнительного канала (SBB/SRD)28
Запрос передачи по дополнительному каналу (SCA/SRTS)29
Готовность к передаче по дополнительному каналу (SCB/SCTS)..... 29
Обнаружение несущей дополнительного канала (SCF/SDCD)29
1.4. Ресурсы IBM PC для последовательной связи29
Сервисы BIOS29
Коммуникационные порты30
Использование прерываний30
Прямое программирование портов в Windows30
Функции Windows31
Глава 2. Протоколы32
Что такое протокол обмена32
ASCII-протокол передачи данных33
Бинарный протокол передачи данных35
Предотвращение потери данных 35
Прерывания и потоки35
Буферизация37
Синхронизация в DOS38
Синхрон изация в Windows38
Обратная связь40
Методы обнаружения ошибок передачи40
Контроль четности41
Контрольная сумма42
Простая контрольная сумма42
Контрольная сумма LRC42
Контрольная сумма CRC1643
Стартовый байт44
Другие способы повышения достоверности данных45
Глава 3. Последовательные протоколы IBM PC46
Мышь46
Базовый протокол Microsoft Mouse46
Протокол Microsoft Plus47
Протокол 3D Serial Mouse47
Протокол PC Mouse48
Совместимость протоколов48
Модем48
Команды управления модемом49
Регистры модемов50
Протоколы передачи файлов50
ASCII-протокол50
Протокол XModem51
Протокол XModem-CRC52
Протокол XModem-1К52
Протокол YModem53
Протокол YModem-G54
Протокол ZModem 54
Протокол BiModem55
Протокол Kermit55
Глава 4. Промышленные последовательные протоколы56
Протокол MODBUS 56
Протокол MODBUS-ASCII57
Протокол MODBUS-RTU58
Поля MODBUS протокола59
Поле ,fАдрес абонента” 59
Поле “Код функции'*60
Поле "Даннме” 60
Поле нКонтрольная сумма*'60
Протокол CAN61
Характеристики протокола CAN62
• 4.2.2. Обмен данными в протоколе CAN63
Обнаружение ошибок в протоколе CAN63
Протокол Profibus64
Глава 5. COM-порты и Plug and Play66
Кратко о Plug and Play66
Запуск процедуры PnP67
Если устройство не находится автоматически68
Где хранится информация о найденных устройствах69
Структура реестра Windows 9869
Структура реестра Windows 200071
Алгоритм Plug and Play для COM-портов73
Инициализация порта (Port initialization)73
Обнаружение устройств (Check for device)73
Установка устройства, фаза 1 (COMport Setup-1)74
Ожидание ответа, фаза 1 (Wait for response-1)74
Установка устройства, фаза 2 (COM port Setup-2)74
Ожидание ответа, фаза 2 (Wait for response-2)75
Получение идентификатора (Collect device ID)75
Проверка отключения (Verify Disconnect)76
Дежурное состояние (Connect Idle)76
Состояние ожидания отключения (DisconnectIdle)76
Формат данных для передачи PnР-идентификатора77
Передача РпР-идентификатора77
Поля РпР-идентификатора78
ПолеOther ID80
ПолеBegin РпР80
ПолеРпР Rev80
ПолеEISA ID80
5,85 ПолеProduct ID81
ПолеExtend81
ПолеSerial Number.81
ПолеClass Name81
ПолеDevice ID82
ПолеUser Name82
ПолеChecksum82
ПолеEnd PnP82
Пример PnP-идентификаторa83
INF-файл иего структура84
СекцияVersion84
СекцияManufacturer85
СекцияDestination Dirs88
Ключ DefaultDescDir88
Ключи file-list-section88
Ключ dirid88
Ключ subdir89
Секция описания модели90
Секции xxx.AddReg и xxx.DelReg91
Секция ххх.LogConfig92
Секция ххх. Copy Files93
Секция Strings94
Связи секций95
Часть II. Практика программирования97
Глава 6. Использование сервисов BIOS99
Подготовка99
Первая программа последовательной связи100
Другие функции BIOS102
Глава 7. Прямое программирование портов104
Коммуникационные порты104
Программа определения наличия СОМ-портов105
Обходим ограничения BIOS106
Чтение и запись с помощью модуля RS232DOS111
Глава 8. Использование обработки прерываний113
Прерывания113
Модуль RS2321nt для работы с прерываниями115
Последовательный обмен с помощью прерываний124
Глава 9. Переход в Windows127
Переходим из DOS в Windows127
Первая программа для Windows131
Перенос программ из Windows 9х в Windows NT/2000142
Получение доступа к портам в Windows 2000/ХР143
Расширение возможностей GivelO148
Работа с драйвером GivelOEx157
Еще немного о прямом доступе к портам165
Глава 10. Использование функций Windows166
Обзор функций Windows для работы с последовательными портами……166
Специальная настройка порта167
Получение состояния линий модема168
104 Используем функции Windows168
Несколько замечаний о контроле четности183
Глава 11. Использование потоков185
Преимущества потоков185
Создание потока опроса порта185
Глава 12. Функции асинхронного доступа и события194
Асинхронный доступ194
События последовательного порта197
Использование событий197
Глава 13. Специальные коммуникационные функции201
13. L Выполнение дополнительных операций201
Прямое управление драйвером203
Обнаружение всех портовсистемы222
Имена портов225
АРС-функции Windows 2000/ХР231
Глава 14. Реализация протоколовобмена234
Прием и передача простого пакета234
Создание компонента241
Реализация ASCI I-протокол а254
Реализация бинарного протокола262
Глава 15. Вычисление контрольных сумм273
Вычисление простой контрольной суммы273
Вычисление LCR274
Вычисление CRC16275
Вычисление CRC32279
Глава 16. Интерфейс RS-485 282
Стандарт RS-485282
Как работает СОМ-порт IBM PC283
Реализация RS-485288
Глава 17. Работа Plug and Play291
Эмулятор устройства291
Установка драйвера устройства300
Обнаружение изменений303
Часть IIL Справочник309
Глава 18. Сервис BIOS INT14H/INT21H311
Сервис BIOS INT14H311
Функция 00: инициализация порта312
Функция 01: посылка символа в порт313
Функция 02: получение символа313
Функция 03: получить статус порта313
Функция 04: расширенная инициализация (System/2)314
Функция 05: расширенное управление портом (System/2)315
182 Сервис BIOS 1NT21H316
Функция ОЗН: вспомогательный ввод317
Функция 04Н: вспомогательный вывод317
Функция 3FH: чтение данных через описатель317
Функция 40Н: запись данных через описатель318
Глава 19. Порты IBM PC319
Порт 3F8H (BasePort+0): данные и делитель320
Порт 3F9H (BasePort+1): разрешение прерываний и делитель320
Порт3FAH (BasePort+2): идентификация прерываний321
Порт3FBH (BasePort+З): управление линией322
5. Порт3FCH (BasePort+4): управление модемом323
19-6. Порт3FDH (BasePort+5): статус линии324
Порт 3FEH (BasePort+б): статус модема325
Порт 3FFH (BasePoiH-7): заглушка325
Скорость передачи325
Порты контроллера прерываний326
Глава 20. Структуры Windows для работы с СОМ-портами328
Структура настроек порта COMMCONFIG328
Структура COMMPROP330
Структура тайм-аутов COMMTIMEOUTS336
Структура статуса порта COMSTA Т.339
Структура DCB340
Глава 21. Функции Windows для работы с СОМ-портами347
Функции CreateFile и CloseHandle: открытие и закрытие порта347
Дополнительные сведения349
Возвращаемое значение349
Пример вызова349
Функция Read File: чтение данных из порта350
Дополнительные сведения351
Возвращаемое значение351
Пример вызова351
Функция WriteFUe\ передача данных352
Дополнительные сведения353
Возврашаемое значение353
Пример вызова353
Функция ReadFileEx: А PC-чтение данных354
Возвращаемое значение356
Дополнительные сведения356
Пример вызова356
Функция WriteFUeEx: АРС-передача данных357
5.1. Возвращаемое значение358
Пример вызова358
Функция BuildCommDCB: создание структуры DCB из строки359
Дополнительные сведения360
Возвращаемое значение360
Пример вызова360
Функция BmldCommDCBAndTimeouts: создание структуры DCB и тайм-аутов из строки 361
Функции SetCommBreak и ClearCommBreak', управление выводом данных362
Возвращаемое значение362
Функция ClearCommError, получение и сброс ошибок порта363
Возвращаемое значение364
Функция Cancello: прерывание асинхронных операций364
Возвращаемое значение364
Функция EscapeCommFunction', управление портом365
Возвращаемое значение366
Функции GetCommMask и SetCommMask: маска вызова событий366
Возвращаемое значение367
Пример вызова367
Функция CreateEvenr. создание объекта ожидания367
Возвращаемое значение368
Пример вызова368
Функция Wait Сотт Event, ожидание события СОМ-порта368
Возвращаемое значение369
Дополнительные сведения369
Пример вызова369
Функции GetCommConfig и SetCommConfig, конфигурирование параметров порта372
Возвращаемое значение372
Пример вызова373
Функция СоттConfigDialo^. диалог конфигурирования порта 374
Возвращаемое значение375
Дополнительные сведения375
Пример вызова375
Функция Get Сотт Properties: прочитать свойства порта375
Возвращаемое значение376
Пример вызова376
Функции GetCommState и SetCommState: состояние порта376
Возвращаемое значение377
Пример вызова 377
Функции GetComm Timeouts и SetComm Timeouts', тайм-ауты порта377
Возвращаемое значение378
Пример вызова378
Функция PurgeComm'. сброс буферов порта378
Возвращаемое значение379
Пример вызова379
Функция SetupСотт\ конфигурирование размеров буферов380
Возвращаемое значение381
Функции GetDefaultCommConfig и Set Default Сотт Config. настройки порта по умолчанию381
Возвращаемое значение382
Функция TransmitCommChar, передача специальных символов382
Возвращаемое значение382
Функция Get Сотт Modemstatus', статус модема383
Возвращаемое значение383
Пример вызова383
Функция WaitForSingleObject, ожидание сигнального состояния объекта384
Возвращаемое значение385
Функция WaitForMultipleObjects', ожидание сигнального состояния объектов385
Возвращаемое значение386
Функция GetOverlappedResult, результат асинхронной операции387
Возвращаемое значение388
Функция DeviceloControb. прямое управление драйвером388
Возвращаемое значение390
Функция Enum Ports: перечисление портов390
Дополнительные сведения391
Возвращаемое значение391
Пример вызова392
Функция Query Dos Device, получение имени устройства по его DOS-имени393
30.1. Возвращаемое значение393
Пример вызова394
Функция Define Dos Device-, операции с DOS-именемустройства395
31.1. Возвращаемое значение395
Пример вызова395
Глава 22. Функции DiviceJoControl для последовательных портов397
Функции драйвера поеледовательного порта397
Структуры драйвера последовательного порта404
Соответствие функций DeviceloControl и Windows API406
Глава 23. АТ-команды и регистры модемов409
Основные АТ-команды модемов409
Основные регистры модемов416
Часть IV. Приложения419
Приложение 1. Микросхемы UART421
Общее описание421
Характерные особенности421
Описание контактов422
Типы микросхем UART426
Приложение 2. Библиотека функций для Pascal427
Приложение 3. Формат SINGLE чисел с плавающейточкой.,..429
Приложение 4. Описание функций Windows для использования в Visual Basic430
Приложение 5. Использование ActiveX-компонента MSComm32433
Приложение 6. Сервис BIOS — обслуживание INT14H436
Приложение 7. Как работает GivelO442
Приложение 8. Реализация процедуры преобразования SINGLE-чисел в строку450
Приложение 9. Индикаторы состояния модема455
Приложение 10. Инструменты456
Порт-монитор456
Разработка драйверов Driver Wizard 457
Разработка драйверов NuMega Driver Studio457
Отладчик NuMega SoftICE458
Эмулятор компьютера Connectix Virtual PC458
Терминал HyperTerminal458
Приложение 11. Формат команды Mode460
Приложение 12. Часто задаваемые вопросы (FAQ)462
FAQ: Общие вопросы462
Что такое СОМ и RS и чем они отличаются?462
Что такое порт?462
Чем “протокол RS-232" отличается от "интерфейса RS-232'*?462
Что такое контрольная сумма и как ее считать?463
Откуда взялось ограничение числа абонентов в протоколе RS-485?463
Почему нужно подключать СОМ-устройства при выключенном питании?463
Откуда берется питание для мыши?463
Что означает режим HANDSHAKING?464
Что такое дуплексная и полудуплексная передача?464
FAQ: Программирование в DOS464
Что такое базовый адрес порта?464
Написал программу обработки прерываний порта, а она периодически “виснет”. Для других прерываний все нормально работает464
При выводе на экран данных, полученных в обработчике прерывания, выводится "мусор"465
Прерывание и основная программа используют один буфер. Как их синхронизировать в DOS?465
Как бы перехватить вывод программы в COM-порт под DOS?465
FAQ: Программирование в Windows 466
Почему в Delphi пропал оператор Port и как тогда обращаться к портам?466
Можно ли использовать прямое обращение к портам в Windows NT/2000?466
Программа асинхронно работает с портом.
В Windows 98 все работало, а в Windows 2000 или “виснет”, или не работает466
Зачем надо использовать WaitForSingleObject, если уже есть вызов GetOverlappedResult466
Как найти все доступные СОМ-порты?466
Как бы перехватить вывод программы в СОМ-порт под Windows?467
Как выключить Plug and Play для COM-порта при загрузке Windows?...467
FAQ: Связь, модемы, терминалы467
Что такое FIFO и FOSSIL?467
Приложение 13. Описание компакт-диска469
Литература и интериет-ресурсы471
Предметный указатель 472