This Blog covers an introduction to Delphi Libusb 1 and details a working Example.
I am introducing the latest Delphi Libusb Header Conversion library that I converted for Windows X86 and X64 and makes use of a full Dll interface which calls the C Libusb library with external Std Calls This is based on the C++ library that has been created by the C++Libusb Team on Github.
Installation of Libusb
The source code and compiled Dll’s are available from my own Github repository named Greg-Bayes - https://github.com/Greg-Bayes/Delphi-libusb
Delphi Libusb Wiki https://github.com/Greg-Bayes/Delphi-libusb/wiki
I have translated sufficient examples to allow for speedy development and I will be adding a few more examples including added support for Cypress EZUSB. If anyone wishes to add any successful examples specific to a device such as an Arduino board, I will happily add it into the repo with the necessary attribution given. These examples are set up in VCL and FMX to make it easier for the user to work with.
On set up, ensure that the libusb1.pas is included in the uses list and place the correct Dll in the exe’s folder then you are ready to code your USB device.
The easiest is to open an example to understand the setup as the DLL’s are prepositioned in the examples to work straight out of the box.
For those who are new to usb I/O feel free to read the background info else skip the Background and scroll to the example further down.
USB background Information
Main communication between devices and client software is conceptualized through the use of pipes. Each pipe is a communication channel between software on the host and an endpoint on a device. Each of the four endpoint types represents a part of a device that fulfils one specific purpose for that device, such as to receive commands or transmit data so they are bio-directional in nature. A full speed device can have up to 32 endpoints i.e. IN and OUT endpoints for numbers 0-15. Though low speed devices can have only three. Endpoint number EP 1 IN and EP 1 OUT are treated as separate endpoints.
All USB devices support endpoint 0 when powered up. This endpoint is the target of the default pipe. After the attachment of a device has been detected, the USB software uses endpoint 0 to initialise the device, perform generic (i.e. non device-specific) configuration, and obtain information about the other endpoints provided by the device. Endpoints are characterised by their endpoint number (set at design time) and bus bandwidth, access frequency, latency and error handling behaviour requirements.
Once the endpoints of a device have been identified and configured, pipes come into existence allowing the client software to communicate with the device. A pipe has associated with it characteristics such as a claim on bus access and bandwidth, the type of transfer, the direction of transfer and the maximum data payload size.
Libusb requires a list of devices to be established and thereafter the device information such as VID id / PID id, Bus and Endpoints can be established per device or for all devices.
USB defines four types of transfer: control transfers which are typically used for command or status operations, interrupt transfers which are initiated by a device to request some action from the host, isochronous transfers which are good for infrequent, single transfers. Libusb 1 provides asynchronous transfers which is not available in Libusb0.
Which is a huge step up as it is non- blocking and notifies on completion and is great for transferring time critical (such as for video and speech), and bulk transfers . The non-blocking nature of this interface allows you to be simultaneously performing I/O to multiple endpoints on multiple devices, without having to use threads.
All transfers take the form of packets, which contain control information, data and error checking fields.
There are also two types of pipes: message pipes and stream pipes. Control transfers are made using message pipes. In a message pipe, the data portion of each packet has some meaning to the USB system software.
Stream pipes are used for interrupt, isochronous, asynchronous and bulk transfers. In a stream pipe, the data portion of the packet has no defined meaning to the USB: the data is merely conveyed between client software and device.
The synchronous I/O interface allows you to perform a USB transfer with a single function call. When the function call returns, the transfer has completed and you can parse the results. The main advantage of this model is simplicity: you did everything with a single simple function call.
This interface has limitations, as your application will sleep inside libusb_bulk_transfer() when using bulk transfer until the transaction has completed. If it takes 3 hours, your application will be sleeping for that long. Execution will be tied up inside the library; the entire thread will be useless for that duration.
Another issue is that by tying up the thread with that single transaction there is no possibility of performing I/O with multiple endpoints and/or multiple devices simultaneously, unless you resort to creating one thread per transaction.
Additionally, there is no opportunity to cancel the transfer after the request has been submitted.
In the interest of being a lightweight library, libusb cannot create threads and can only operate when your application is calling into it. Your application must call into libusb1 from its main loop when events are ready to be handled, or you must use some other scheme to allow libusb1 to undertake whatever work needs to be done.
Delphi Libusb also needs to be called into at certain fixed points in time in order to accurately handle transfer timeouts.
Memory handling becomes more complex. You cannot use stack memory unless the function with that stack is guaranteed not to return until the transfer call-back has finished executing.
You generally lose some linearity from your code flow because submitting the transfer request is done in a separate function from where the transfer results are handled. This becomes particularly obvious when you want to submit a second transfer based on the results of an earlier transfer.
Internally, libusb1's synchronous interface is expressed in terms of function calls to the asynchronous interface.
Devices and interfaces
Each usb device is manipulated with a libusb_device() and libusb_device_handle() objects in libusb1.pas. The libusb1.pas API ties an open device to a specific interface. This means that if you want to claim multiple interfaces on a device, you should open the device multiple times to receive one libusb_dev_handle() for each interface you want to communicate with. Don't forget to call libusb_claim_interface().
This requires you have to claim the interface before any operation on the device can be performed, and also, you have to release the claimed interface after you are finished with the device.
Each device has its own configuration values, like vendor id, product id, etc. We use these settings to discover the desired device and work with.
OK, now that you have the basic understanding of the USB device, let’s now apply the library to a Real World Application.
Getting a Device List
Add the library to your uses list and ensure that the DLL is in the same folder/path as your exe.
Interface uses …., libusb1;
First set up the variables as shown below. The plibusb_context is the context that is used to initialize libusb DLL and to free the devices. pplibusb_device is the pointer to the libusb_device that will be used to obtain the device list.
var i, count: ssize_t; r: integer; devs: pplibusb_device; arrdev: array of plibusb_device; // set dynamic array context: plibusb_context;
We need to set the context to nil. This will ensure that the pointer is nil before initializing the libusb_init() method. An error free initialization will result in 0. If the Libusb dll has been initialized, the context and the address of the pplibusb_device is obtained and used to call the Libusb_get_device_list() to which we obtain a count of the devices available on the Windows OS system.
context := nil; r := libusb_init(context); if r <> 0 then exit else count := libusb_get_device_list(context, @devs);
As we will need to populate a dynamic array, we have to set the array length before using it. So we have to get the full count of devices available on the system which will become our array length. Set the length of the array of plibusb_device - arrdev prior to getting the device list as the count obtained.
We no longer need this instance of the Libusb_Device_list ,. It must be freed directly.
setlength(arrdev, count); libusb_free_device_list(devs, 1);
We now need to repeat getting the libusb_device_list but this time we place the address of the array of plibusb_device - arrdev device list. Thereafter we populate the array with individual Libusb_device information. Once done, the data is available for use.
if count < 1 then exit else libusb_get_device_list(context, @arrdev);
Now we can iterate through the array to extract the Device bus no, VID Id and PID Id. We call a function getdev() ad unction that we will create which will populate all the required device information.
for i := 0 to count - 1 do getdev(arrdev[i]);
In C the method free_device_list would automatically dereference the array. Delphi will not allow this process as we are using a dynamic array so we use an alternate dereference method unref_device()for each device within the array. Only then can we exit Delphi libusb by calling libusb_exit() using the original context.
for I := high(arrdev) to low(arrdev) do libusb_unref_device(arrdev[i]); libusb_exit(context); end; end;
Here we set up the function getdev() as used in Step 6. Here we add many variables and we will refer to them as we run through this function method.
procedure getdev(dev: plibusb_device); var f:Uint8_t; i, j, k, r, s: integer; desc: libusb_device_descriptor; config: plibusb_config_descriptor; inter: plibusb_interface; interdesc: plibusb_interface_descriptor; epdesc: plibusb_endpoint_descriptor; path: array [0 .. 8] of byte; begin
We first activate the device descriptor of each device. A result of 0 means it is error free. Then we can get the plibusb_device_descriptor using libusb_device record information and any of these can be called per device. Here is a list of what can be called.
For more info refer to the Wiki ..
bLength: uint8_t; (Size of this descriptor (in bytes)
bDescriptorType: uint8_t; ( USB specification release number in binary-coded decimal. A value of $0200 indicates USB 2.0, $0110 indicates USB 1.1)
bDeviceClass: uint8_t; ( USB-IF subclass code for the device, qualified by the value)
bDeviceSubClass: uint8_t; ( USB-IF protocol code for the device, qualified by the bDeviceClass )
bDeviceProtocol: uint8_t; (Maximum packet size for endpoint 0 )
bMaxPacketSize0: uint8_t; ( USB-IF vendor ID )
idVendor: uint16_t; (USB-IF product ID )
idProduct: uint16_t; (Device release number in binary-coded decimal )
bcdDevice: uint16_t; (Index of string descriptor describing manufacturer )
iManufacturer: uint8_t; (Index of string descriptor describing product)
iProduct: uint8_t; (Index of string descriptor containing device serial number )
iSerialNumber: uint8_t; ( Number of possible configurations )
So here we call the most relevant ….
r := libusb_get_device_descriptor(dev, @desc); if r <> 0 then exit else Memo1.lines.add('No of possible configurations : ' + inttostr(desc.bNumConfigurations) + ' Device Class : ' + inttostr(desc.bDeviceClass));
At the same time we can call the libusb_get_bus_number() and libusb_get_port_numbers(). Extracting the VID id and PID id , it is best to use the inttohex conversion which will produce the correct string Number.
f:= libusb_get_bus_number(dev); Memo1.lines.add('Bus : ' + inttostr(libusb_get_bus_number(dev)) + ' Device : ' + inttostr(libusb_get_device_address(dev)) + ' Vender ID : ' + inttohex(desc.idVendor) + ' Product ID : ' + inttohex(desc.idProduct)); r := libusb_get_port_numbers(dev, @path, sizeof(path)); end; end;
OK, now compile it and checked the device list.
This now forms the basis for all the libusb1 applications to be developed on.
June 2019Delphi A Professional VCL DBGrid Part Four
May 2019Delphi A Professional VCL DBGrid Part Three
April 2019Delphi A Professional VCL DBGrid Part Two
March 2019Delphi A Professional VCL DBGrid Part One
November 2018Delphi VCL Buttons in DBGrid
October 2018Two Helper Apps for Delphi LibUSB
September 2018Delphi Libusb Library Introduction
August 2018Delphi Object directly to a Json string in a REST Client
July 2018Delphi FMX Leaflet Plotter using OSM Maps
June 2018C2PAS32 Convertor Application
May 2018Delphi PDF Embedded viewer with PDF.js
March 2018Delphi FMX - Changing TCharacter to TCharHelper
January 2018Delphi FMX Dashboard using Chart.JS
December 2017PHP Slim REST Server & Delphi Auth Part 5
November 2017Delphi FMX REST Client App Part 4
October 2017Delphi VCL REST Pricing Client App Part 3
September 2017Delphi REST VCL Client Basic Auth Part 2B
August 2017Delphi REST Client Part 2A
July 2017PHP REST Server and Delphi Client Intro
June 2017Delphi SQLite Encryptor-Decryptor Tool
May 2017Create a Visual IP Address Geolocation with PHP
March 2017PHP Downloader using Countdown timer
January 2017Morris Charts and PHP-PDO
December 2016CSS to create a functional Toggle Button