Monday, 24 January 2011

Interfacing with a USB parallel port adapter and Lazarus.

The parallel (centronics / LPT / printer) port is disappearing on computers. I must admit that I use this port sometimes to quickly build an interface with my electronic stuff. So lately I had bought a new very compact computer, only to finding out that by no means I could easily interface with my projects, unless I bought some ready from the shelve hardware and NI labview to get this done with four figures dollar amounts. So I figured I better buy a cheap 1 figure dollar amount USB parallel interface adapter on ebay ($8,00 + shipment) and decided to put a little effort for once and for who knows how long usb and parallel adapters will be around, to get a cheap and robust parallel interface with my computer.
The problem is that the usb port is thought to be a little difficult to interface and a thorough explanation on the web on how to get a usb port running step by step is far from complete. This tutorial is written in order to remove the mysticism around the usb and make it accessible to a broader public.
This article is meant to be a step by step guide to get the usb parallel port running to interface electronics/digital projects. It is also very affordable for students in a classroom setting.

I chose ubuntu linux 10.04 as operating system. So I downloaded an ubuntu ISO, burnt it to a CD and installed this OS on my new computer. Then I did the necessary updates to get the OS up to date. The computer is equipped with an '86 32 bit quadruple thread Intel D510 processor, and the programming language I want to use is Freepascal and I use Lazarus as the editor.
Lazarus is a pascal based programming language and I just love Pascal, and I also know why but I don't want to go into details about that. Just hail the people who spent so much time on making this programming tool readily available. Understand that this beautiful biblical name "Lazarus" got me inspired too. For me it all fit the bill perfectly.
Of course I will get into trouble by choosing this programming language that has not so much support, but ok I just had to use it and I want it to be cheap and robust, which is a difficult combination.
So After installing ubuntu, I selected Lazarus from the repository and look... it installed all by itself with just one click.
Image and video hosting by TinyPic

Took two hours to download, though it is well worth the wait.
I had used Lazarus before on SuSe, but I remember I needed to install first FPC and then compile all repositories, that took nearly a day to install. That was seven years ago. Thumbs up for ease.

Now find the port number for usb ports. Duh.. I don't know nothing about usb, so I learned I had to forget everything about good old 8086 architecture with XT/AT slots and port numbers, if I wanted to do anything with usb.

Usb is more of a data structure. So if you want to know more about usb, there is a lot on the web available to read. Go ahead and google around, however if you want to get your project interfaced, just forget about this for now and continue to read here to get results.

To make a very big story small, I found a pascal unit that gives you access to the usb subroutines necessary to program the usb ports. The name of the unit is libusb and it compiles with freepascal.
Here is a transcript of the version that I use, just copy it over and save it as libusb.pp and put it together with your program.
Honor to whom deserves honor for writing this library, thank you mr. Erdfelt..
Credits: * Copyright (c) 2000-2003 Johannes Erdfelt

And thanks to mr. Zimmermann for converting to Pascal.
_______________________
unit libusb;
interface

{$LINKLIB c}
{$LINKLIB usb}
{ based on libusb 0.1.12 manually edited by Uwe Zimmermann, 2006-06-28}
{ Automatically converted by H2Pas 1.0.0 from /usr/include/usb.h
The following command line parameters were used:
-d
-l
usb
-o
libusb2.pp
-p
/usr/include/usb.h}

Const PATH_MAX = 4096;
{ Pointers to basic pascal types, inserted by h2pas conversion program.}
Type
PLongint = ^Longint;
PSmallInt = ^SmallInt;
PByte = ^Byte;
PWord = ^Word;
PDWord = ^DWord;
PDouble = ^Double;
u_int8_t = byte;
u_int16_t = word;
u_int32_t = dword;
size_t = u_int32_t;

{
Type
Pusb_bus = ^usb_bus;
Pusb_config_descriptor = ^usb_config_descriptor;
Pusb_ctrl_setup = ^usb_ctrl_setup;
Pusb_descriptor_header = ^usb_descriptor_header;
Pusb_dev_handle = ^usb_dev_handle;
Pusb_device = ^usb_device;
Pusb_device_descriptor = ^usb_device_descriptor;
Pusb_endpoint_descriptor = ^usb_endpoint_descriptor;
Pusb_hid_descriptor = ^usb_hid_descriptor;
Pusb_interface = ^usb_interface;
Pusb_interface_descriptor = ^usb_interface_descriptor;
Pusb_string_descriptor = ^usb_string_descriptor;
}
{$IFDEF FPC}
{$PACKRECORDS C}
{$ENDIF}
{
* Prototypes, structure definitions and macros.
*
* Copyright (c) 2000-2003 Johannes Erdfelt
*
* This library is covered by the LGPL, read LICENSE for details.
*
* This file (and only this file) may alternatively be licensed under the
* BSD license as well, read LICENSE for details.
}
// {$ifndef __USB_H__}
// {$define __USB_H__}
// {$include }
// {$include }
// {$include }
// {$include }
{
* USB spec information
*
* This is all stuff grabbed from various USB specs and is pretty much
* not subject to change
}
{
* Device and/or Interface Class codes
}
{ for DeviceClass }
const
USB_CLASS_PER_INTERFACE = 0;
USB_CLASS_AUDIO = 1;
USB_CLASS_COMM = 2;
USB_CLASS_HID = 3;
USB_CLASS_PRINTER = 7;
USB_CLASS_PTP = 6;
USB_CLASS_MASS_STORAGE = 8;
USB_CLASS_HUB = 9;
USB_CLASS_DATA = 10;
USB_CLASS_VENDOR_SPEC = $ff;
{
* Descriptor types
}
USB_DT_DEVICE = $01;
USB_DT_CONFIG = $02;
USB_DT_STRING = $03;
USB_DT_INTERFACE = $04;
USB_DT_ENDPOINT = $05;
USB_DT_HID = $21;
USB_DT_REPORT = $22;
USB_DT_PHYSICAL = $23;
USB_DT_HUB = $29;
{
* Descriptor sizes per descriptor type
}
USB_DT_DEVICE_SIZE = 18;
USB_DT_CONFIG_SIZE = 9;
USB_DT_INTERFACE_SIZE = 9;
USB_DT_ENDPOINT_SIZE = 7;
{ Audio extension }
USB_DT_ENDPOINT_AUDIO_SIZE = 9;
USB_DT_HUB_NONVAR_SIZE = 7;
{ All standard descriptors have these 2 fields in common }
USB_MAXENDPOINTS = 32;
USB_MAXALTSETTING = 128;
USB_MAXINTERFACES = 32;
USB_MAXCONFIG = 8;
USB_ENDPOINT_ADDRESS_MASK = $0f;
USB_ENDPOINT_DIR_MASK = $80;
USB_ENDPOINT_TYPE_MASK = $03;
USB_ENDPOINT_TYPE_CONTROL = 0;
USB_ENDPOINT_TYPE_ISOCHRONOUS = 1;
USB_ENDPOINT_TYPE_BULK = 2;
USB_ENDPOINT_TYPE_INTERRUPT = 3;
USB_REQ_GET_STATUS = $00;
USB_REQ_CLEAR_FEATURE = $01;
{ 0x02 is reserved }
USB_REQ_SET_FEATURE = $03;
{ 0x04 is reserved }
USB_REQ_SET_ADDRESS = $05;
USB_REQ_GET_DESCRIPTOR = $06;
USB_REQ_SET_DESCRIPTOR = $07;
USB_REQ_GET_CONFIGURATION = $08;
USB_REQ_SET_CONFIGURATION = $09;
USB_REQ_GET_INTERFACE = $0A;
USB_REQ_SET_INTERFACE = $0B;
USB_REQ_SYNCH_FRAME = $0C;
USB_TYPE_STANDARD = $00 shl 5;
USB_TYPE_CLASS = $01 shl 5;
USB_TYPE_VENDOR = $02 shl 5;
USB_TYPE_RESERVED = $03 shl 5;
USB_RECIP_DEVICE = $00;
USB_RECIP_INTERFACE = $01;
USB_RECIP_ENDPOINT = $02;
USB_RECIP_OTHER = $03;
{
* Various libusb API related stuff
}
USB_ENDPOINT_IN = $80;
USB_ENDPOINT_OUT = $00;
{ Error codes }
USB_ERROR_BEGIN = 500000;
type
Pusb_descriptor_header = ^usb_descriptor_header;
usb_descriptor_header = record
bLength : u_int8_t;
bDescriptorType : u_int8_t;
end;
{ String descriptor }
Pusb_string_descriptor = ^usb_string_descriptor;
usb_string_descriptor = record
bLength : u_int8_t;
bDescriptorType : u_int8_t;
wData : array[0..0] of u_int16_t;
end;
{ HID descriptor }
{ u_int8_t bReportDescriptorType; }
{ u_int16_t wDescriptorLength; }
{ ... }
Pusb_hid_descriptor = ^usb_hid_descriptor;
usb_hid_descriptor = record
bLength : u_int8_t;
bDescriptorType : u_int8_t;
bcdHID : u_int16_t;
bCountryCode : u_int8_t;
bNumDescriptors : u_int8_t;
end;
{ Endpoint descriptor }
type
Pusb_endpoint_descriptor = ^usb_endpoint_descriptor;
usb_endpoint_descriptor = record
bLength : u_int8_t;
bDescriptorType : u_int8_t;
bEndpointAddress : u_int8_t;
bmAttributes : u_int8_t;
wMaxPacketSize : u_int16_t;
bInterval : u_int8_t;
bRefresh : u_int8_t;
bSynchAddress : u_int8_t;
extra : Pbyte;
extralen : longint;
end;
{ in bEndpointAddress }
type
Pusb_interface_descriptor = ^usb_interface_descriptor;
usb_interface_descriptor = record
bLength : u_int8_t;
bDescriptorType : u_int8_t;
bInterfaceNumber : u_int8_t; (**)
bAlternateSetting : u_int8_t;
bNumEndpoints : u_int8_t;
bInterfaceClass : u_int8_t;
bInterfaceSubClass : u_int8_t;
bInterfaceProtocol : u_int8_t;
iInterface : u_int8_t;
endpoint : Pusb_endpoint_descriptor;
extra : Pbyte;
extralen : longint;
end;
type
Pusb_interface = ^usb_interface;
usb_interface = record
altsetting : Pusb_interface_descriptor;
num_altsetting : longint;
end;
Pusb_config_descriptor = ^usb_config_descriptor;
usb_config_descriptor = record
bLength : u_int8_t;
bDescriptorType : u_int8_t;
wTotalLength : u_int16_t;
bNumInterfaces : u_int8_t;
bConfigurationValue : u_int8_t;
iConfiguration : u_int8_t;
bmAttributes : u_int8_t;
MaxPower : u_int8_t;
theInterface : Pusb_interface;
extra : Pbyte;
extralen : longint;
end;
Pusb_device_descriptor = ^usb_device_descriptor;
usb_device_descriptor = record
bLength : u_int8_t;
bDescriptorType : u_int8_t;
bcdUSB : u_int16_t;
bDeviceClass : u_int8_t;
bDeviceSubClass : u_int8_t;
bDeviceProtocol : u_int8_t;
bMaxPacketSize0 : u_int8_t;
idVendor : u_int16_t;
idProduct : u_int16_t;
bcdDevice : u_int16_t;
iManufacturer : u_int8_t;
iProduct : u_int8_t;
iSerialNumber : u_int8_t;
bNumConfigurations : u_int8_t;
end;

Pusb_ctrl_setup = ^usb_ctrl_setup;
usb_ctrl_setup = record
bRequestType : u_int8_t;
bRequest : u_int8_t;
wValue : u_int16_t;
wIndex : u_int16_t;
wLength : u_int16_t;
end;

Pusb_bus = ^usb_bus;
Pusbdevice = ^usbdevice;
usbdevice = record
next : Pusbdevice;
prev : Pusbdevice;
filename : array[0..(PATH_MAX+1)-1] of char;
bus : Pusb_bus;
descriptor : usb_device_descriptor;
config : Pusb_config_descriptor;
dev : pointer;
devnum : u_int8_t;
num_children : byte;
children : ^Pusbdevice;
end;
usb_bus = record
next : Pusb_bus;
prev : Pusb_bus;
dirname : array[0..(PATH_MAX+1)-1] of char;
devices : Pusbdevice;
location : u_int32_t;
root_dev : Pusbdevice;
end;
Pusb_dev_handle = ^usb_dev_handle;
usb_dev_handle = record
{undefined structure}
end;
{ Variables }
var
usb_busses : Pusb_bus;cvar;external;
{ C++ extern C conditionnal removed }
{ Function prototypes }
{ usb.c }
function usb_open(dev:Pusbdevice):Pusb_dev_handle;cdecl;external;
function usb_close(dev:Pusb_dev_handle):longint;cdecl;external;
function usb_get_string(dev:Pusb_dev_handle; index:longint; langid:longint; buf:Pchar; buflen:size_t):longint;cdecl;external;
function usb_get_string_simple(dev:Pusb_dev_handle; index:longint; buf:Pchar; buflen:size_t):longint;cdecl;external;
{ descriptors.c }
function usb_get_descriptor_by_endpoint(udev:Pusb_dev_handle; ep:longint; _type:byte; index:byte; buf:pointer;
size:longint):longint;cdecl;external;
function usb_get_descriptor(udev:Pusb_dev_handle; _type:byte; index:byte; buf:pointer; size:longint):longint;cdecl;external;
{ .c }
function usb_bulk_write(dev:Pusb_dev_handle; ep:longint; bytes:Pchar; size:longint; timeout:longint):longint;cdecl;external;
function usb_bulk_read(dev:Pusb_dev_handle; ep:longint; bytes:Pchar; size:longint; timeout:longint):longint;cdecl;external;
function usb_interrupt_write(dev:Pusb_dev_handle; ep:longint; bytes:Pchar; size:longint; timeout:longint):longint;cdecl;external;
function usb_interrupt_read(dev:Pusb_dev_handle; ep:longint; bytes:Pchar; size:longint; timeout:longint):longint;cdecl;external;
function usb_control_msg(dev:Pusb_dev_handle; requesttype:longint; request:longint; value:longint; index:longint;
bytes:Pchar; size:longint; timeout:longint):longint;cdecl;external;
function usb_set_configuration(dev:Pusb_dev_handle; configuration:longint):longint;cdecl;external;
function usb_claim_interface(dev:Pusb_dev_handle; theInterface:longint):longint;cdecl;external;
function usb_release_interface(dev:Pusb_dev_handle; theInterface:longint):longint;cdecl;external;
function usb_set_altinterface(dev:Pusb_dev_handle; alternate:longint):longint;cdecl;external;
function usb_resetep(dev:Pusb_dev_handle; ep:dword):longint;cdecl;external;
function usb_clear_halt(dev:Pusb_dev_handle; ep:dword):longint;cdecl;external;
function usb_reset(dev:Pusb_dev_handle):longint;cdecl;external;
const
LIBUSB_HAS_GET_DRIVER_NP = 1;
function usb_get_driver_np(dev:Pusb_dev_handle; theInterface:longint; name:Pchar; namelen:dword):longint;cdecl;external;
const
LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP = 1;
function usb_detach_kernel_driver_np(dev:Pusb_dev_handle; theInterface:longint):longint;cdecl;external;
function usb_strerror:Pchar;cdecl;external;
procedure usb_init;cdecl;external;
procedure usb_set_debug(level:longint);cdecl;external;
function usb_find_busses:longint;cdecl;external;
function usb_find_devices:longint;cdecl;external;
Function usb_device(Dev:Pusb_dev_handle):Pusbdevice; cdecl; external;
Function usb_get_busses : Pusb_bus; cdecl; external;
implementation
end.
_________________


Now this unit will not work by itself alone. You will have to install the c++ libusb libraries. For this we go to the software repository program again and install the libusb libraries as shown in the picture below.

Image and video hosting by TinyPic

From this point on, fpc can compile the unit libusb that we just created.

Image and video hosting by TinyPic

For the hardware, you need to find a usb to parallel adapter that you can verify that it is working in windows as well. The first adapter 'Belkin US$25' that I bought, I could not get it to work. Finally I tried to install it on a Microsoft windows computer. Windows detected the device, installed it and tried to use it on a printer, but it never worked. I even tried to connect pin1 (Strobe) to pin 10 (Ack) and pin 11 (busy) to ground, but it hung. So basically to avoid much frustration, make sure your adapter is working somewhere. If you have no printer with a parallel connector, then simply connect pin1 to pin 10 and pin 11 to ground, and the computer thinks that there is a printer attached. Then if you print a file to the usb printer port it will purge the printer spooler. If the usb printer adapter does not work, then your printjob will get stuck in the printer spooler.

Now we need to know something about our usb adapter. For this I open a terminal and give the command lsusb.
This is the result. Bus 004 device 002 is my printer port. Note these numbers can change if you replug.

Image and video hosting by TinyPic

At this moment I need to know everything about my printer port usb adapter. This info is in a datastructure in the port and can be read and displayed on the screen. Therefore the next command is lsusb -s 4:2 -v . The 4:2 is bus#:device# This will give all the internal info on the usb adapter. Note I will use the unique idProduct number being hexadecimal 7584. (You will need to modify the sample program to program your unique idProduct). I also need the output endpoint being 2. That could be anything on your end too. This endpoint is the physical output of my adapter. There are two endpoints. One input and one output. I use the output in my example. This datastructure says that I have one endpoint with address 0x02= 2, that is the output of my adapter and I can write 0x20 = 32 tokens per write cycle in a bulk transfer command.

Image and video hosting by TinyPic

Now we need a program example that you can modify and make work for your purpose.
Here is the example program to write one byte of data to the port. It worked for me.
Make sure that you run the compiled program as root. If you have no root access it will simply not work.

____________________
program project1;

{$mode objfpc}{$H+}

uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes,libusb,sysutils,crt,dos;

procedure sendbyte (b:char);
var
pbus : pusb_bus;
pdev : pusbdevice; dev : usbdevice; udev : pusb_dev_handle;
stop : Boolean; ret: longint;
send_string : Pchar;

begin
usb_init(); usb_find_busses(); usb_find_devices(); pbus := usb_get_busses; stop:=false;
{ usb_set_debug (3); } {unquote for debug usb commands}
stop:=false;
repeat {scan all the usb devices}
pdev:=pbus^.devices;
dev:=pdev^;
repeat
udev := usb_open(@dev);
If dev.descriptor.idProduct=$7584 then stop:=true; {here I use the idProduct that comes with my usb/centronics adapter you should use your own number that appeared with lsusb as described above}
usb_close(udev);
pdev:=pdev^.next;
until (pdev=nil) or stop;
pbus:=pbus^.next;
until (pbus=nil) or stop;
If stop then
Begin
udev := usb_open(@dev);
{Note that when plugging the usb, the os will claim it, so you need to detach the usb from the Operating System therefore you need to be root to run this program}
ret:=usb_detach_kernel_driver_np (udev, dev.config[0].theinterface[0].altsetting[0].binterfacenumber);


ret := usb_set_configuration(udev,dev.config[0].bconfigurationvalue);
ret:=usb_claim_interface (udev,dev.config[0].theinterface[0].altsetting[0].binterfacenumber);
ret := usb_set_altinterface (udev,0);
ret := usb_resetep (udev,dev.config[0].theinterface[0].altsetting[0].endpoint[1].bendpointaddress);
send_string:= @b; {This is what we will send}
ret := usb_bulk_write(udev,dev.config[0].theinterface[0].altsetting[0].endpoint[1].bendpointaddress,send_string,1,1000);


ret:= usb_close(udev);
end;
end;

{$IFDEF WINDOWS}{$R project1.rc}{$ENDIF}
var q:byte;

begin
q:=255;
sendbyte (#00);
sendbyte (chr(q));
end.
______________________________

Note:
Make sure that you disconnect pin1 from pin 10, if not then the data will appear only during a couple of microseconds. When I call this procedure to write the data, I rely on the fact that the endpoint is reset before writing. The reset will clear the buffer and leave my device ready to accept the token that will be available on the pins until I reset my endpoint.

Note:
As you can see, this program finds the usb device every time you call it, then it opens the device, then it resets it and finally it writes the information.
This is the easiest way to do it, but it is slooooooow according to my scope you can send about 200 byte per second.

Image and video hosting by TinyPic

If you want speed, then you will need to solder a latch on the output and handshake with the strobe pin and maybe you need the busy pin if you have slow logic.. Of course at that moment you need to open the device at the beginning of the program and close it when you are done.


Very important note:
You need to be root to run this program.
If you are not root, it will not work.
This is because you need take the usb device away from the os and claim it for yourself without using any driver. After compilation run the program with the command

sudo project1
or open a terminal and make it a root terminal.
sudo su
or run Lazarus with root rights and compile from there and debug as well.
sudo startlazarus

Good luck.

disclaimer. !I am not responsible for your actions based on this text. Everything you do from here, you do it at your own risk. These procedures include manipulation of electronics and can lead to electric shock, electrocution, burns of the skin or damage to equipment if not properly done!

2 comments:

  1. hi, this is good topic but i want to know about USB interfacing with VB 6.0. in windows xp2, If have you any idea or link related this topic, then please send me...

    ReplyDelete
  2. Hello friend your post was great, I'm starting to work with usb how do I send a string instead of a byte?

    ReplyDelete