The objective while creating USB2LPT was not to adopt application software. In many cases, this will work well. But for some reasons, USB2LPT specific application software will have some advantages as noted above.
The strategy to communicate to USB2LPT is easy and portable accross
programming languages as C, Delphi, VisualBasic, VBA, and LabVIEW.
No additional DLLs are needed. You open communication by
CreateFile of device "\\.\LPT1"
(or "LPT2"
if second device etc.),
and transfer data over the DeviceIoControl Windows API.
InpOut32.DLL
(or similar),
you don't need administrative privileges, and you won't open a security hole.
Note that InpOut32.DLL
opens a security hole by itself;
application code may format your hard disk.
haccess
holding the handle to USB2LPT device is handy.
You can open regular parallel ports with same system call. Because that's not intended yet, a USB2LPT check should follow.HANDLE hAccess; for (int n= 9 ; n; n--) { // try backwardsTCHAR DevName[ 12 ];wsprintf(DevName, "\\\\.\\LPT%u",n);hAccess = CreateFile(DevName,GENERIC_READ|GENERIC_WRITE, 0 ,NULL,OPEN_EXISTING,0 ,0 );if (hAccess!=INVALID_HANDLE_VALUE) goto found; } hAccess = 0 ;found:
An “upper filter driver” for regular parallel ports is indended to be written that is compatible with USB2LPT. So an application software may use the same security-hole-free API for parallel ports later.// Read the 8051 XRAM or ATmega flash address 6 where you get the firmware date as FAT date. // If this fails, this is not a USB2LPT. #include "usb2lpt.h"WORD addr = 6 ;WORD date = 0 ; // Date as FAT dateDWORD BytesRet; if (DeviceIoControl(hAccess,IOCTL_VLPT_XramRead/*0x22228E*/,&adr,sizeof(adr),&date,sizeof(date),&BytesRet,NULL)) { // this is a USB2LPT device of any version, and the firmware date can be made visible using FILETIME ft; DosDateTimeToFileTime(date, 0 ,&ft);SYSTEMTIME st; FileTimeToSystemTime(&ft,&st); TCHAR s[ 20 ]; // the string buffer where the date is putGetDateFormat(LOCALE_USER_DEFAULT, 0 ,&st,NULL,s,20 );// ... }else{ // this is a standard parallel port or something else }
Where is: a a relative address byte:void outb(BYTE a, BYTE b) { BYTE IoData[ 2 ];DWORD BytesRet; IoData[ 0 ] = a;IoData[ 1 ] = b;DeviceIoControl(hAccess,IOCTL_VLPT_OutIn/*0x222010*/,IoData,sizeof(IoData),NULL, 0 ,&BytesRet,NULL);}
You may wondering that a DLL call is much simpler.
But beware! For opening a DLL, you most often need extra coding with LoadLibrary
and GetProcAddress
!
Same as for OUT, a is a relative address. The operation reads the levels at port pins (not necessarily the same as the data output). The results are:BYTE inb(BYTE a) { BYTE IoData[ 1 ];DWORD BytesRet; IoData[ 0 ] = a|0x10 ; // set the bit for read operationsDeviceIoControl(hAccess,IOCTL_VLPT_OutIn/*0x222010*/,IoData,sizeof(IoData),IoData,sizeof(IoData),&BytesRet,NULL); return IoData[ 0 ];}
See this a bit too simple wrapper DLL implementation
as a reference.
When you look at the code snippets, you will see that all is done with one IOCTL codes (0x222010) and up to two buffers. You can concatenate IN and OUT instructions in any order upto buffer sizes of 64 Bytes (more is not tested yet, but upto 4096 bytes should work).
You combine the accesses you need by concatenating OUT addresses, following OUT data, and IN addresses into the IOCTL input puffer (that is, the data that goes OUT to the USB device), and reserve one byte per IN access in the IOCTL output buffer (that is, the data that comes IN from the USB device). That's all! In this way, you generate microcode that is executed by USB2LPT's firmware.
For example:
Bear in mind that reading from data port does not read the pin states if the port is in ECP mode. Instead, the FIFO is read.// Read all 17 port pin states with one DeviceIoControl invocation void GetPinStates(BYTE states[ 3 ]) {static const BYTE SendBytes[ 3 ] = {0x10 ,0x11 ,0x12 };DWORD BytesRet; DeviceIoControl(hAccess,IOCTL_VLPT_OutIn/*0x222010*/,SendBytes, 3 ,states,3 ,&BytesRet,NULL);}
The “backdoor” for doing this are simply some more addresses:
The address 11 is currently not used.
The bits of the USB2LPT Feature Register:
High-Speed, Full-Speed only: Open-collector simulation makes the port compatible to some hardware that requires 5 V HIGH levels. Otherwise, output HIGH level is limited to 3.3 V.
Low-Speed only: The simulation of open-collector should only be used
with external pull-up resistors, as internal pullups are very weak (about 40 kΩ).
The regular output HIGH level is 5 V. If you need 3.3 V outputs,
you should modify the USB2LPT device by inserting a voltage-dropping dual diode
D2 like BAV199 (cut SJ3),
and change the USB pullup resistor R1 to 1.5 kΩ.
See schematic.
Conditional abort and Loop are not implemented yet, and behaviour may change. Nesting may not be allowed.
The desired waveform must be loaded into a specific RAM area. The needed GPIF outputs (CLKOUT, IFCLK, CTL0..2) and inputs (STAT0..2, maybe IFCLK) are routed to control vs. status ports. See schematic.
While switched to High-Speed transfer, the normal parallel-port emulation is disabled.
The exact specification will be made later.
For Full-Speed and High-Speed devices, the API can access:
[bRequest=0xA0]
Restricted 8051 XRAM area (write-only)
[bRequest=0xA2, wIndex==0]
Entire boot EEPROM location (read and write)
[bRequest=0xA2, wIndex!=0]
Any optional I²C device on I²C bus
with automated 1-byte and 2-byte address generation
and auto-packetizing in any 2n quantity.[bRequest=0xA3, wIndex==0]
Entire 8051 XRAM area (read and write),
inclusive program memory, special-function register, USB I/O buffer area,
and GPIF table location
[bRequest=0xA3, wIndex!=0]
RAM/IRAM area (read and write, TODO)
For Low-Speed (V-USB based) devices, the API can access:
[bRequest=0xA2]
EEPROM memory (read-write)
[bRequest=0xA3, wIndex==0]
Flash memory (read-only)
[bRequest=0xA3, wIndex!=0]
RAM and mapped registers (read-write)