Работа с параллельным портом в Windows NT
Александр Тарасенко, Санкт-Петербург
Данная статья посвящена вопросам использования параллельных коммуникационных портов в приложениях Windows NT. В статье рассматриваются способы управления параллельными портами используя стандартные системные драйверы. Не рассматриваются вопросы аппаратного обеспечения и протоколы передачи. Предполагается, что с этими вопросами читатель знаком
Содержание:
1. Стандартные системные драйвера для работы с параллельными портами
2. Работа с параллельным портом через драйвер parclass.sys
2.1 Открытие и закрытие порта
2.2 Запись данных и чтение
2.3 Как получить информацию о состоянии устройства
2.4 Как узнать режим, в котором находится порт?
2.5 Как установить режим порта?
2.6 Как установить адрес для чтения и записи в ECP или EPP режиме?
3 Примеры
3.1 Работа с принтером в текстовом режиме
1. Стандартные системные драйвера для работы с параллельными портами.
В системе существует два стандартных драйвера, обеспечивающих работу параллельных портов и устройств, подключенных к этим портам – parclass.sys и parport.sys. На самом деле, в системе могут быть и другие драйвера, обеспечивающие работу аппаратуры, реализующей параллельные порты, но их работа прозрачна для остальных компонентов системы. Драйвер parclass.sys используется драйверами устройств более высокого уровня, например драйвером сканера, или приложениями для прямого доступа к параллельному порту (row access).
Рассмотрим функции драйверов parclass.sys и parport.sys. Parport.sys предназначен для управления самим параллельным портом, т.е железом. Может показаться, что этот драйвер должен обеспечивать доступ через порты ввода/вывода к регистрам параллельного порта – данных, статуса и управляющему и предоставлять этот сервис вам. Как бы не так! В системе Windows NT все не так просто и прямолинейно. Этот драйвер выполняет совсем другие функции – он поддерживает PnP, регулирует доступ к портам, их захват и освобождение, контролирует режим работы каждого из присутствующих портов.
Parclass.sys – это базовый драйвер устройства, подключенного к параллельному порту. Почувствуйте разницу: parport.sys – драйвер самого порта, parclass.sys – драйвер устройства, подключенного к порту. Parclass.sys обеспечивает низкоуровневый доступ (row access) к устройству, подключенному к параллельному порту. Хотя при этом не стоит думать опять же, что этот драйвер позволит напрямую работать с регистрами параллельного порта. Когда вы из приложения вызывает функцию CreateFile("LPT1", … ) – вы работаете с объектом ядра – именованным устройством, созданным именно драйвером parclass.sys. Короче говоря, если вы собираетесь поуправлять неким устройством через параллельный порт из приложения, вы будете взаимодействовать именно с parclass.sys. Изучением интерфейса этого драйвера мы и займемся в дальнейшем. Если вас все же не оставляет мысль напрямую работать с регистрами параллельного порта – вам придется разработать собственный драйвер, другой дороги у вас нет. Но прежде чем заняться этим – подумайте! Я вас уверяю, что в 99% случаев такой необходимости нет.
Отметим, две особенности:
1)Обычно для устройств, подключенных к параллельному порту, разрабатывается специфичный драйвер, относящий это устройство к определенному классу устройств, например к классу принтеров.
2)Сам порт для приложения выглядит как устройство с именем LPTn. Вы можете спросить: "а где же устройство?". Считайте в данном случае устройством LPT разъем. Все что находится за ним, для системы будет являться черным ящиком – подключайте к нему что хотите (например, микроконтроллер). Этой ситуацией мы и будем в основном интересоваться.
2. Работа с параллельным портом через драйвер parclass.sys
2.1 Открытие и закрытие порта.
Тут все просто, открываем доступ к устройству. Это осуществляется посредством функции CreateFile c параметром LPTn, где LPTn существующее имя для объекта устройства:
HANDLE hLpt = CreateFile( "LPT1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,0, NULL );
При удачном завершении операции будет возвращен файловый дескриптор, через который мы и будем управлять параллельным портом. Остается только добавить, что после использования следует освободить порт посредством вызова CloseHandle(hLpt).
2.2 Запись данных и чтение
Запись и чтение происходит как при обращении к обычному файлу. Для этого используются функции WriteFile и ReadFile.
2.3 Как получить информацию о состоянии устройства?
Для этого нам нужно познакомится с функцией, которая не применяется при работе с файлами – DeviceIoContol:
BOOL DeviceIoControl(
HANDLE hDevice, //Дескриптор устройства
DWORD dwIoControlCode, //Управляющий код, номер операции
LPVOID lpInBuffer, //Указатель на буфер с данными для передачи
DWORD nInBufferSize, //Размер буфера
LPVOID lpOutBuffer, //Указатель на буфер с полученными от устройства данными
DWORD nOutBufferSize, //Размер буфера
LPDWORD lpBytesReturned, //Размер возвращенного данных
LPOVERLAPPED lpOverlapped //Указатель на структуру для асинхронных операций
);
Управляющие коды для работы с драйвером parclass.sys объявлены в заголовочном файле . Этот файл входит в состав DDK. Если у Вас не инсталлирован DDK, то управляющие коды и структуры придется задать вручную. Для получения/установки информации о статусе устройства служат следующие коды:
#define IOCTL_PAR_QUERY_INFORMATION CTL_CODE( FILE_DEVICE_PARALLEL_PORT, 1, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define IOCTL_PAR_SET_INFORMATION CTL_CODE (FILE_DEVICE_PARALLEL_PORT, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
* Не забудьте подключить заголовочный файл !!!
Управляющие структуры:
typedef struct _PAR_QUERY_INFORMATION{
UCHAR Status;
} PAR_QUERY_INFORMATION, *PPAR_QUERY_INFORMATION;
typedef struct _PAR_SET_INFORMATION{
UCHAR Init;
} PAR_SET_INFORMATION, *PPAR_SET_INFORMATION;
* В данном случае можно и не определять дополнительные типа данных, поскольку они являются псевдонимами для беззнакового однобайтового целого.
Назначение бит в байте (как это определено в )
Как можно видеть, тут намешаны биты как из регистра состояния, так и из управляющего регистра. Поскольку мы договаривались не рассматривать различные режимы работы и протоколы обмена, будем считать, что назначения этих битовых полей понятно. Отмечу лишь, что некоторые биты доступны только для чтения (вернее сказать, попытка установить их будет проигнорирована).
А вот как это делается:
Обратная операция (PAR_SET_INFORMATION) имеет смысл только при подаче сигнала сброса, т.е бит PARALLEL_INIT обязательно должен быть установлен.
2.4 Как узнать режим, в котором находится порт?
Для этого служит команда IOCTL_IEEE1284_GET_MODE. Она определена следующим образом:
#define IOCTL_IEEE1284_GET_MODE CTL_CODE (FILE_DEVICE_PARALLEL_PORT, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
При вызове функции DeviceIoControl информация будет возвращена в структуре, определенной следующем образом:
Одно поле – установленный протокол для чтения, другое – протокол записи. Определены следующие протоколы (опять же в ntddpar.h, так что имеет смысл использовать этот заголовок J ) :
Получим, что по умолчанию порт находится в следующем режиме: для чтения NIBBLE (т.е полубайтный режим ввода), для записи – в режиме CENTRONICS (опять же, надеюсь читатель помнит, что это за режим).
Если вы во время работы изменили режим работы порта, вам, вероятно, будет интересно, какой же режим был установлен изначально (хотя бы чтобы по окончанию работы вернуть порт в исходное состояние). Для этого служит команда IOCTL_PAR_GET_DEFAULT_MODES, определенная как:
#define IOCTL_PAR_GET_DEFAULT_MODES CTL_CODE( FILE_DEVICE_PARALLEL_PORT, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
Параметры этой команды аналогичны команде IOCTL_IEEE1284_GET_MODE.
2.5 Как установить режим порта?
Для этого опять же есть специальная команда: IOCTL_IEEE1284_NEGOTIATE.
#define IOCTL_IEEE1284_NEGOTIATE CTL_CODE( FILE_DEVICE_PARALLEL_PORT, 6, METHOD_BUFFERED, FILE_ANY_ACCESS).
Для использования этой команды нужно задать как входной параметр, так и выходной буфера. Эти буфера должны иметь размер не менее размера структуры типа PARCLASS_NEGOTIATION_MASK (поскольку данные упакованы именно в эту структуру). Отметим, что как следует из названия команды, порт попробует "договориться" с устройством об используемом протоколе. Если устройство не сможет поддержать инициативу драйвера, запрошенный протокол установлен не будет и в поле структуры PARCLASS_NEGOTIATION_MASK будет возвращен 0 (NONE).
2.6 Как установить адрес для чтения и записи в ECP или EPP режиме?
Следуя нашему уговору, не обсуждаем, что такое адрес, а сразу берем быка за рога:
#define IOCTL_PAR_SET_WRITE_ADDRESS CTL_CODE( FILE_DEVICE_PARALLEL_PORT, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_PAR_SET_READ_ADDRESS CTL_CODE( FILE_DEVICE_PARALLEL_PORT, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
Адрес имеет размер 1 байт и передается параметр lpInBuffer функции DeviceIoControl. Например, так:
С помощью управляющих кодов, объявленных следующим образом,:
#define IOCTL_PAR_GET_READ_ADDRESS CTL_CODE( FILE_DEVICE_PARALLEL_PORT, 14, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_PAR_GET_WRITE_ADDRESS CTL_CODE( FILE_DEVICE_PARALLEL_PORT, 15, METHOD_BUFFERED, FILE_ANY_ACCESS)
можно выяснить установленные адреса для чтения и для записи соответственно. Я думаю нет нужды говорить, что адрес будет возвращен через параметр lpOutBuffer функции DeviceIoControl.
3 Примеры
3.1 Работа с принтером в текстовом режиме
В данном примере мы рассмотрим, как работать с принтером в текстовом режиме. На самом деле, ваш любимый принтер может этого и не захотеть (большинство современных принтеров работают в графическом режиме). Но передо мной в данный момент стоит старое доброе печатающее устройство: EPSON LQ-100. Он работает по умолчанию в текстовом режиме, воспринимая ASCII коды, и поддерживает протокол Centronix. Так что с ним нам будет просто:
HANDLE hLpt =
CreateFile( "LPT1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL );
char buf[0x100]; //помните, выделять массивы на стеке не очень здорово!!!
sprintf(buf, "hello printer\ngood bye printer");
BOOL res =
WriteFile(hLpt, &buf, strlen(buf), &ret, NULL);
CloseHandle(hLpt);
На листочке мы увидим (если не забудем его подсунуть жадному устройству):
hello printer
good bye printer
3.2 Возможно, будет продолжение :)
По крайней мере, хотелось еще рассмотреть реализацию SPI протокола для обмена с микроконтроллерами.