USB-Logo The API for USB2LPT (Application Programmer's Interface) 25pol. SubD-Buchse

There is a specific API to access USB2LPT. You need this API to do: This API is relatively simple and uses almost no symbolic constants.

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.

Parallel port compatible access

This access enables using USB2LPT like a real parallel port (inclusive ECP and EPP if needed), without “dirty” redirection. You don't even need 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.

Opening of USB2LPT

A global variable haccess holding the handle to USB2LPT device is handy.
HANDLE hAccess;

 for (int n=9; n; n--) {	// try backwards
  TCHAR 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:
You can open regular parallel ports with same system call. Because that's not intended yet, a USB2LPT check should follow.
// 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 date
 DWORD 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 put
   GetDateFormat(LOCALE_USER_DEFAULT,0,&st,NULL,s,20);
   // ...
 }else{
  // this is a standard parallel port or something else
 }
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.

A single OUT access

This function looks like follows:
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);
}
Where is: a a relative address byte: Note that three bits at a=2 are inverted! You can avoid inversion by a “feature register“, see later. Moreover, you may output data to status port (a=1), but without precautions (later), this data will never appear somewhere.

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!

A single IN access

This function looks like follows:
BYTE inb(BYTE a) {
 BYTE IoData[1];
 DWORD BytesRet;
 IoData[0] = a|0x10;	// set the bit for read operations
 DeviceIoControl(hAccess,IOCTL_VLPT_OutIn/*0x222010*/,IoData,sizeof(IoData),IoData,sizeof(IoData),&BytesRet,NULL);
 return IoData[0];
}
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: Note that one bit at status port (a=1) and three bits at control port (a=2) are inverted! You can avoid inversion by an “extra” register, see later.

See this a bit too simple wrapper DLL implementation [Auge] as a reference.

Combined access

Singe accesses have no improvement for speed compared to port access trapping! The opposite may the case, OUT accesses are not cached and concatenated automatically.

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:

// 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);
}
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.

Extended port access

A USB2LPT device ist a Multi-I/O device with 17 digital I/Os; newer revisions have even 20 useable I/Os.
Using the extended port access you can set the data, data direction, and the pull-up (applies to low-speed) for every single pin.

The “backdoor” for doing this are simply some more addresses:

Direction bits = 1 are outputs, otherwise inputs. The three “lower” bits of status ports are ground pins at SubD pins 21, 22, and 23 (see schematic).

The address 11 is currently not used.

The bits of the USB2LPT Feature Register:

  1. Simulation of open-collector for data port
  2. Simulation of open-collector for control port in SPP mode
  3. Simulation of open-collector for control port in other modes
  4. unused
  5. Serialized: The USB device enumerates with its serial number. While Windows normally “track” devices using the topology (of USB ports and hubs) used, the system will now change to “track” a particular device by its unique serial number. Of-course it will be in effect after next USB enumeration, i.e. device detection process.
  6. DarkBlue: darker blue LED using 12.5 % pulse width modulation (High-Speed only)
  7. DirectIO (no inversions; access to three extra I/Os located at bits 0..2 of status port, no automated open-collector simulation)
The simulation of open-collector enables collision-free wired-AND logic. That is needed for some hardware.

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.

Wait Cycles and Bit Access

Combination of many OUT and IN accesses into one packet may require some sort of program controlling. There are some additional opcodes for doing this. These extra addresses behave like OUT Addresses (bit 4 cleared), i.e. a data byte follows, and there is no answer.

Conditional abort and Loop are not implemented yet, and behaviour may change. Nesting may not be allowed.

High-Speed only:

Planned: GPIF boosted High-Speed (48 MByte/s) transfers

High-Speed transfers are such using the GPIF (General Purpose Interface). Data rates in maximum speed will become possible, even via the 8-bit bus. This is 48 MByte/s. Fast enough for filling large RAM-based FPGAs (Spartan-3) with configuration data in a fraction of a second, and also for streaming video data from/to that FPGA.

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.

Not planned: Use of peripheral features (Timer, ADC etc.)

Such use is by far much more than what USB2LPT is planned for, and is not compatible between Low-Speed, Full-Speed and High-Speed. If someone need such, one can extend the firmware. See below.

Read and write RAM, EEPROM, or Flash

Fully implemented. See “brenner.c”.

For Full-Speed and High-Speed devices, the API can access:

This API allows both self-programming the EEPROM (i.e. firmware update or deletion) but also temporary firmware changes (by writing to the microcontroller's RAM). The latter option enables using the USB2LPT device as a fully customizeable microcontroller device. Either you can use the GPIF interface, or do some bit-banging in firmware, or both.
The CY7C68013A doesn't expose timers or serial interfaces to its general-purpose I/O pins, so bit-banging is always necessary to implement some sort of serial protocol like SPI, I²C, or JTAG. However, bit-banging in a microcontroller using assembly language can be still quite fast.

For Low-Speed (V-USB based) devices, the API can access:

Self-programming via this API is not possible, but you can start the bootloader by writing a magic byte 0x42 into EEPROM address 0, then genereate a USB RESET, then access via bootloadHID protocol.