Bayesean Blog - Desktop, Mobile and IOT Developer Blog


Delphi Libusb Library Introduction

Posted on 27th Sep 2018 in Delphi FMX, Delphi VCL


LIBusb37.png

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

Communication

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.

Synchronous Interface

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.

Asynchronous Transfers

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

Step 1

Add the library to your uses list and ensure that the DLL is in the same folder/path as your exe.

Interface
uses
  …., libusb1;

Step 2

 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;

Step 3

 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);

Step 4

 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); 

Step 5

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);

Step 6

 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]);

Step 7

 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;

 

Step 8

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

 

Step 9

 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 )

  bNumConfigurations: uint8_t;

 

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.

 

Download Code

 

 

Happy Coding

 


Lauro      Commented   6 months ago Reply

Congratulations on the Code! This example can be used in DBGrid to make the Tree feature. | Customers A | -Sales $ 100.00

Add a Comment

9+5

Recent News

Delphi Delimited String to Fields
Delphi A Professional VCL DBGrid Part Four
Delphi A Professional VCL DBGrid Part Three
Delphi A Professional VCL DBGrid Part Two
Delphi A Professional VCL DBGrid Part One
Delphi VCL Buttons in DBGrid
Two Helper Apps for Delphi LibUSB
Delphi Libusb Library Introduction

Categories

Bootstrap 4
Delphi VCL
Delphi FMX
Ajax
Bootstrap 3
CSS
XE4>Delphi > XE4
Delphi < XE4
PHP

Archives

August 2019

Delphi Delimited String to Fields

June 2019

Delphi A Professional VCL DBGrid Part Four

May 2019

Delphi A Professional VCL DBGrid Part Three

April 2019

Delphi A Professional VCL DBGrid Part Two

March 2019

Delphi A Professional VCL DBGrid Part One

November 2018

Delphi VCL Buttons in DBGrid

October 2018

Two Helper Apps for Delphi LibUSB

September 2018

Delphi Libusb Library Introduction

August 2018

Delphi Object directly to a Json string in a REST Client
Delphi using Environment Variables in your App

July 2018

Delphi FMX Leaflet Plotter using OSM Maps

June 2018

C2PAS32 Convertor Application
C to Delphi Open Source Convertors Shootout
Delphi command-line programs with DOSCommand

May 2018

Delphi PDF Embedded viewer with PDF.js

March 2018

Delphi FMX - Changing TCharacter to TCharHelper
Make Your Delphi App POP using Javascript!

January 2018

Delphi FMX Dashboard using Chart.JS
Delphi FMX Form Docking

December 2017

PHP Slim REST Server & Delphi Auth Part 5

November 2017

Delphi FMX REST Client App Part 4

October 2017

Delphi VCL REST Pricing Client App Part 3

September 2017

Delphi REST VCL Client Basic Auth Part 2B

August 2017

Delphi REST Client Part 2A
PHP PDO REST Server Part 1

July 2017

PHP REST Server and Delphi Client Intro

June 2017

Delphi SQLite Encryptor-Decryptor Tool
Updating Applications Manifest using Delphi

May 2017

Create a Visual IP Address Geolocation with PHP

March 2017

PHP Downloader using Countdown timer
PHP File Downloader from a Inbox Selection

February 2017

Javascript Image-File Uploader with ThumbViewer

January 2017

Morris Charts and PHP-PDO

December 2016

CSS to create a functional Toggle Button