Применение драйвера параллельного порта ПК

March 30, 2012 by admin Комментировать »

В практике  любителей радио , измерительной и робототехники наибольшей популярно стью пользуется параллельный порт персонального компьютера. Многие устройства как со зданные, так и разрабатываемые,  управляются от параллельного порта. Изучив основы раз работки  драйверов,  мы может без особого труда создать несложный  драйвер  для устройства, присоединенного к параллельному порту ПК. Сам по себе, без дополнительной электроники, параллельный порт может служить неплохим генератором звуковых  колебаний, в качестве анализатора входных цифровых сигналов или использоваться  для функционирования  более сложных схем, например, аналого цифровых преобразователей. Рассмотрим два аппаратно программных  проекта, работающих с параллельным портом принтера, для которых создадим простейшие драйверы.

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

500Гц. На основе этого примера читатели легко могут создать широкодиапазонный генера тор от нескольких до 500 Гц. Прямоугольные импульсы вырабатываются на выходе регистра данных (адрес 0x378)  параллельного порта, поэтому аппаратной части в смысле дополни тельных микросхем  здесь  нет. Хотя, если вы  планируете подключать  выходы генератора к значительной нагрузке,  то на выходах порта установите дополнительные буферные усилите ли формирователи, способные обеспечить требуемый ток в нагрузке.

Для реализации  программной  части нам  потребуется создать  драйвер  параллельного

порта и приложение пользователя, работающее с этим драйвером. Начнем с драйвера. Са мый простой метод генерации импульсов на каком либо выводе порта – прочитать значение защелки порта, инвертировать его и переслать обратно в параллельный порт. Таким обра зом, можно получить один прямоугольный импульс. Для генерации непрерывной последова тельности импульсов с определенной частотой необходимо периодически  повторять опера цию «считывание инверсия запись» в регистре 0x378. Период повторения импульсов можно задавать либо в самом драйвере устройства либо в приложении пользователя. Мы будем

использовать второй способ, задавая интервал времени в приложении пользователя – этот метод несравненно  проще, чем тот, который можно было бы реализовать в самом драйвере.

Вначале разработаем  драйвер (назовем  его lptgen.sys), который будет работать с «вирту альным» устройством lptgen, в качестве  которого  фактически  будет выступать порт вывода параллельного   порта  ПК.  Наш  драйвер   будет  обрабатывать   IOCTL команду  IOCTL_WRITE в пакете запроса IRP_MJ_DEVICE_CONTROL. Вот исходный текст драйвера:

#include  <ntddk.h>

#define  IOCTL_WRITE   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

#define  NT_DEVICE_NAME   L"\\Device\\lptgen"

#define  DOS_DEVICE_NAME   L"\\DosDevices\\lptgen"

#define DATA   0x378

VOID

lptgen_Unload   (IN  PDRIVER_OBJECT  DriverObject)

{

UNICODE_STRING  DosDeviceName;

RtlInitUnicodeString(&DosDeviceName,  DOS_DEVICE_NAME); IoDeleteSymbolicLink(&DosDeviceName); IoDeleteDevice(DriverObject->DeviceObject);

}

NTSTATUS

lptgen_Create(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

lptgen_Close(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

lptgen_Ioctl(IN  PDEVICE_OBJECT  DeviceObject,

IN PIRP Irp)

{

PIO_STACK_LOCATION  pIoStack   = IoGetCurrentIrpStackLocation(Irp); ULONG   ctlCode   = pIoStack->Parameters.DeviceIoControl.IoControlCode;

if (ctlCode == IOCTL_WRITE)

{

   asm {

mov  DX, DATA in    AL, DX not  AL

out  DX, AL

}

}

Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

DriverEntry(IN  PDRIVER_OBJECT  DriverObject, IN PUNICODE_STRING  RegistryPath)

{

PDEVICE_OBJECT  DeviceObject; UNICODE_STRING  NtDeviceName; UNICODE_STRING  DosDeviceName; NTSTATUS  status;

RtlInitUnicodeString(&NtDeviceName,  NT_DEVICE_NAME);

status =  IoCreateDevice(DriverObject, sizeof(MY_DEVICE_EXTENSION),

&NtDeviceName, FILE_DEVICE_UNKNOWN,

0, FALSE,

&DeviceObject);

if (!NT_SUCCESS(status))

{

IoDeleteDevice(DeviceObject);

return status;

}

RtlInitUnicodeString(&DosDeviceName,  DOS_DEVICE_NAME);

status = IoCreateSymbolicLink(&DosDeviceName, &NtDeviceName);

if (!NT_SUCCESS(status))

{

IoDeleteSymbolicLink(&DosDeviceName); IoDeleteDevice(DeviceObject);

return status;

}

DeviceObject->Flags  &=   ~DO_DEVICE_INITIALIZING; DeviceObject->Flags  |=  DO_BUFFERED_IO;

DriverObject->MajorFunction[IRP_MJ_CREATE]  = lptgen_Create; DriverObject->MajorFunction[IRP_MJ_CLOSE]     = lptgen_Close; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = lptgen_Ioctl; DriverObject->DriverUnload  = lptgen_Unload;

return status;

}

Во многом исходный текст драйвера нам знаком,  поэтому я остановлюсь на реализации функции lptgen_Ioctl. Здесь мы используем блок ассемблерных команд. Вначале содержимое регистра  0x378  помещается  в регистр  AL командой  in, где оно инвертируется,  после чего выво дится обратно в регистр по команде out. Здесь мы предполагаем, что в качестве базового  реги стра параллельного порта используется регистр с адресом 0x378.  Если на вашем компьютере базовый адрес другой, например, 0x278, то необходимо переопределить константу DATA.

Таким  образом,  каждый  раз  при  вызове  функции DeviceIoControl() из  приложения

пользователя с кодом команды IOCTL_WRITE в драйвере будет выполняться инверсия выхо

дов регистра 0x378.

Перейдем теперь к разработке приложения пользователя. Здесь, как  и в предыдущих приложениях, мы будем использовать  динамическую  загрузку  выгрузку  драйвера, а для опе раций с устройством использовать  функцию WINAPI DeviceIoControl(),  которой  будем переда вать  код  команды  IOCTL_WRITE. Вот исходный текст пользовательской  программы:

#include  <windows.h>

#include  <stdio.h>

#include  <conio.h>

#define  IOCTL_WRITE   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

SC_HANDLE  scm,  svc; SERVICE_STATUS   ServiceStatus;

void  main(void)

{

HANDLE  fh; DWORD   bytes;

scm =  OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if (!scm)

{

printf("Cannot open SCM!\n");

return;

}

svc  =  CreateService(scm, "lptgen", "lptgen",

SERVICE_ALL_ACCESS,

SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, "i:\\lptgen.sys",

NULL, NULL, NULL, NULL, NULL);

if (!svc)

{

printf("Cannot create  service!\n"); CloseHandle(scm);

return;

}

StartService(svc, 0, NULL); CloseServiceHandle(svc); CloseServiceHandle(scm);

fh  = CreateFile("\\\\.\\lptgen",

GENERIC_READ  | GENERIC_WRITE,

0, NULL, OPEN_EXISTING,

0,

NULL);

if (fh   !=  INVALID_HANDLE_VALUE)

{

printf("Type any key to  exit\n");

while  (! _kbhit())

{

DeviceIoControl(fh, IOCTL_WRITE, NULL,

0, NULL,

0,

&bytes,

NULL);

Sleep(1);

};

}

} CloseHandle(fh);

scm =  OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if (!scm)

{

printf("Cannot open SCM!\n");

return;

}

svc  = OpenService(scm,   "lptgen", SERVICE_ALL_ACCESS); ControlService(svc,  SERVICE_CONTROL_STOP,  &ServiceStatus); DeleteService(svc);

CloseServiceHandle(svc); CloseServiceHandle(scm);

}

Здесь основная работа выполняется в цикле while (! _kbhit()). Этот цикл выполняется до тех пор (как и генерация сигнала на выходе параллельного порта), пока пользователь не на жмет любую клавишу. Функция Sleep(1) выполняет задержку  на 1 миллисекунду, а поскольку для генерации  полного периода колебаний требуется два цикла, то в результате получим пе риод колебаний 2 мс, что соответствует частоте 500Гц.  Для получения частоты, например, в 100Гц  аргумент функции Sleep() должен иметь значение 5 и т. д.

Наш второй проект намного сложнее. Мы разработаем программную часть аналого циф рового  преобразователя  на  микросхеме  LTC1286, который  управляется с параллельного порта ПК. Этот проект, но с использованием драйвера PortTalk, мы анализировали в главе 3. Напомню, как выглядит аппаратная часть проекта (рис. 7.13):

Рис. 7.13

Схема аппаратной части АЦП   

Сначала разработаем драйвер параллельного порта (назовем его adc1286.sys), который будет собирать  данные  с АЦП по запросу  IRP_MJ_DEVICE_CONTROL и передавать их приложе нию пользователя. Исходный текст драйвера приведен далее:

#include  <ntddk.h>

#define  IOCTL_READ   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

#define  NT_DEVICE_NAME   L"\\Device\\adc1286"

#define  DOS_DEVICE_NAME   L"\\DosDevices\\adc1286"

typedef struct  _MY_DEVICE_EXTENSION

{

ULONG   L1;

}  MY_DEVICE_EXTENSION,   *PMY_DEVICE_EXTENSION;

#define DATA   0x378

#define STATUS  0x379

VOID

adc_Unload  (IN  PDRIVER_OBJECT  DriverObject)

{

UNICODE_STRING  DosDeviceName;

RtlInitUnicodeString(&DosDeviceName,  DOS_DEVICE_NAME); IoDeleteSymbolicLink(&DosDeviceName); IoDeleteDevice(DriverObject->DeviceObject);

}

NTSTATUS

adc_Create(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

adc_Close(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return  STATUS_SUCCESS;

ОСНОВЫ РАЗРАБОТКИ ДРАЙВЕРОВ УСТРОЙСТВ В WINDOWS

}

NTSTATUS

adc_IoctlR(IN  PDEVICE_OBJECT  DeviceObject, IN PIRP Irp)

{

PMY_DEVICE_EXTENSION   dx  = (PMY_DEVICE_EXTENSION)DeviceObject-

>DeviceExtension;

PIO_STACK_LOCATION  pIoStack   = IoGetCurrentIrpStackLocation(Irp);

ULONG   ctlCode   = pIoStack->Parameters.DeviceIoControl.IoControlCode; ULONG   OutputLength  = pIoStack-

>Parameters.DeviceIoControl.OutputBufferLength;

PUCHAR   buf  = (PUCHAR)Irp->AssociatedIrp.SystemBuffer; PULONG  Lbuf = &dx->L1;

dx->L1 = 0;

OutputLength  = 4;

if (ctlCode  == IOCTL_READ)

{

   asm {

push  EBX

mov    DX, DATA xor    AX, AX bts   AX, 7

out    DX, AL

btr   AX, 7 out    DX, AL

mov    BX, 15 next:

xor    AX, AX

mov    DX, DATA

btr   AX, 6 out    DX, AL

mov    DX, STATUS

in      AL, DX bt      AX, 3 rcl  CX, 1

mov    DX, DATA

bts     AX, 6 out    DX, AL

dec    BX

jnz    next

mov    DX, DATA

bts     AX, 7 out    DX, AL

pop   EBX

and   CX, 0x0FFF

mov      EDX,    DWORD PTR   Lbuf mov      WORD PTR   [EDX], CX

}

RtlMoveMemory(buf,

(PUCHAR)&dx->L1, OutputLength);

}

Irp->IoStatus.Information  = OutputLength; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,  IO_NO_INCREMENT); return STATUS_SUCCESS;

}

NTSTATUS

DriverEntry(IN  PDRIVER_OBJECT  DriverObject, IN PUNICODE_STRING  RegistryPath)

{

PDEVICE_OBJECT  DeviceObject; UNICODE_STRING  NtDeviceName; UNICODE_STRING  DosDeviceName; NTSTATUS  status;

RtlInitUnicodeString(&NtDeviceName,  NT_DEVICE_NAME);

status =  IoCreateDevice(DriverObject, sizeof(MY_DEVICE_EXTENSION),

&NtDeviceName, FILE_DEVICE_UNKNOWN,

0, FALSE,

&DeviceObject);

if (!NT_SUCCESS(status))

{

IoDeleteDevice(DeviceObject);

return status;

}

RtlInitUnicodeString(&DosDeviceName,  DOS_DEVICE_NAME);

status = IoCreateSymbolicLink(&DosDeviceName, &NtDeviceName);

if (!NT_SUCCESS(status))

{

ОСНОВЫ РАЗРАБОТКИ ДРАЙВЕРОВ УСТРОЙСТВ В WINDOWS

IoDeleteSymbolicLink(&DosDeviceName); IoDeleteDevice(DeviceObject);

return status;

}

DeviceObject->Flags  &=   ~DO_DEVICE_INITIALIZING; DeviceObject->Flags  |=  DO_BUFFERED_IO;

DriverObject->MajorFunction[IRP_MJ_CREATE]  = adc_Create; DriverObject->MajorFunction[IRP_MJ_CLOSE]     = adc_Close; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = adc_IoctlR; DriverObject->DriverUnload  = adc_Unload;

return status;

}

В этом драйвере основная работа выполняется функцией adc_IoctlR, которая обрабаты вает пакет запроса IRP_MJ_DEVICE_CONTROL с кодом  IOCTL команды  IOCTL_READ. Поскольку драйвер должен возвращать данные приложению, то мы определили беззнаковую целочис ленную 32 битовую переменную L1 в области расширения. Здесь нужно сделать одно очень важное замечание: драйвер ядра, как  правило, обрабатывает целочисленные данные, хотя может работать и с функциями математического  сопроцессора.  В данном случае мы будем передавать в программу пользователя 32 битовый результат аналого цифрового преобразо вания, а окончательную  обработку результата будет выполнять приложение  пользователя. В конечном  итоге на экран  дисплея будет выведено значение результата преобразования в формате вещественного числа с плавающей точкой.

Второй важный  момент. При работе с АЦП LTC1286 мы используем ассемблерный код. Для такого класса приложений реального времени размер и эффективность программного кода имеют первостепенное  значение, хотя в других случаях вполне возможно использова ние функций _inp() и _outp языка C++.

Обратите внимание,  как  из ассемблерного  кода мы получаем доступ к переменной L1 –

многие ошибки в работе драйвером при использовании ассемблерного кода связаны именно с неправильной адресацией внешних по отношению к ассемблерному  блоку команд.  Хоть это и необязательно, но желательно сохранять содержимое регистров в стеке командой push перед началом обработки  данных и выталкивать  их оттуда командой pop после окончания работы. Это особенно важно  при разработке  драйверов,  в которых работает несколько про граммных потоков. В данном случае, мы сохраняем только содержимое регистра EBX.

Теперь  перейдем  к приложению  пользователя, которое  получает данные от нашего

устройства adc1286.  Исходный текст приложения показан  далее:

#include  <windows.h>

#include  <stdio.h>

#define  IOCTL_READ   CTL_CODE(FILE_DEVICE_UNKNOWN,\

0x801,\ METHOD_BUFFERED,\ FILE_ANY_ACCESS)

SC_HANDLE  scm,  svc; SERVICE_STATUS   ServiceStatus;

void  main(void)

{

HANDLE  fh; int binRes; float total;

DWORD   bytes;

scm =  OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if (!scm)

{

printf("Cannot open SCM!\n");

return;

}

svc  =  CreateService(scm, "adc1286", "adc1286", SERVICE_ALL_ACCESS,

SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, "i:\\adc1286.sys", NULL,

NULL,

NULL, NULL, NULL);

if (!svc)

{

printf("Cannot create  service!\n"); CloseHandle(scm);

return;

}

StartService(svc, 0, NULL); CloseServiceHandle(svc); CloseServiceHandle(scm);

fh  = CreateFile("\\\\.\\adc1286", GENERIC_READ  | GENERIC_WRITE,

0,

NULL, OPEN_EXISTING,

0, NULL);

if (fh   !=  INVALID_HANDLE_VALUE)

{

DeviceIoControl(fh, IOCTL_READ, NULL,

0,

&binRes, sizeof(binRes),

&bytes, NULL);

total = 5.0  / 4096.0  * binRes;

printf("ADC  LTC1286  result: %5.3f V\n",  total);

}

CloseHandle(fh);

scm =  OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if (!scm)

{

printf("Cannot open SCM!\n");

return;

}

svc  = OpenService(scm,   "adc1286",  SERVICE_ALL_ACCESS); ControlService(svc,  SERVICE_CONTROL_STOP,  &ServiceStatus); DeleteService(svc);

CloseServiceHandle(svc); CloseServiceHandle(scm);

}

В этом приложении,  как  и в предыдущих, используется WINAPI функция DeviceIoControl(), которая  посылает  устройству  команду  IOCTL_READ. Драйвер устройства adc1286 возвращает

32 битовый двоичный результат, который нужно преобразовать в вещественное число с пла

вающей точкой. По этой причине в приложении объявлены две переменные:

int binRes;

float total;

Переменная binRes будет содержать двоичный результат преобразования, а переменная total  – окончательное  вещественное  значение  аналого цифрового  преобразования.   Для

12 битового АЦП с напряжением смещения, равным 5В, значение результата в формате пла

вающей точки будет вычисляться следующим образом:

total = 5.0  / 4096.0  * binRes;

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

Для дальнейшего совершенствования ваших навыков по разработке драйверов Windows всем желающим могу посоветовать обратиться на сайт www.microsoft.com, на котором, поми мо стандартной документации, имеются многочисленные статьи с описанием практических аспектов создания и отладки драйверов.

Источник:  Магда Ю. С. Компьютер  в домашней лаборатории.  – М.: ДМК Пресс, 2008. – 200 с.: ил.

Оставить комментарий

микросхемы мощности Устройство импульсов питания пример приемника провода витков генератора выходе напряжение напряжения нагрузки радоэлектроника работы сигнал сигнала сигналов управления сопротивление усилитель усилителя усиления устройства схема теория транзистора транзисторов частоты