View the ADU series of USB based Data Acquisition Products
Introduction
Communicating with USB devices via software involves a
few simple steps. Unlike RS232 based devices which are
connected to physical COM ports, USB devices are
assigned a logical
handle
by operating systems when they are first plugged in.
This process is known as
enumeration.
Once a USB device has been
enumerated, it is ready for use by the
host computer software. For the host application
software to communicate with the USB device, it must
first obtain the handle assigned to the USB device
during the enumeration process. The handle can be
obtained using an
open function
along with some specific information about the USB
device. Information that can be used to obtain a handle
to a USB device include,
serial number,
product ID,
or
vendor ID.
Once we obtain a USB device handle, we can read and
write information to and from the USB device via our
application. Once the application has finished with all
communication with the USB device, the handle is closed.
The handle is generally closed when the application
terminates.
The
sample source code outlines the basics of communicating
directly with an ADU device on Linux and OS X using C
and libhidapi. Basics of opening a USB device handle,
writing and reading data, as well as closing the handle
of the ADU usb device is provided as an example. An
alternate way of working with ADU devices in Linux is to
use the adutux kernel driver to access the device as a
file descriptor (outlined here:
https://www.ontrak.net/Linux/APG.htm). The preferred
method of interfacing with ADU devices on Windows is via
the DLL.
All
source code is provided so that you may review details
that are not highlighted here.
Lets have a look at the code......
This example illustrates the basics of reading and
writing to ADU devices using the hidapi library.
HIDAPI
is a C library that provides simple access to HID
compliant USB devices (https://github.com/libusb/hidapi).
We will need a
vendor ID and product
ID
in order to open the USB device. The VENDOR_ID define
will always remain the same as this is OnTrak's USB
vendor ID, however, PRODUCT_ID must be set to match the
product that is connected via USB. See this link for a
list of OnTrack product IDs:
https://www.ontrak.net/Nodll.htm.
//
#include "hidapi/hidapi.h"
#define VENDOR_ID 0x0a07
#define PRODUCT_ID 200
#define COMMAND_SIZE 8
Next, let's declare our USB device handle. This handle
will be used for all of our interactions with the USB
device via HIDAPI (opening, closing, reading and writing
commands). We'll also initialize the HIDAPI library and
check the returned result.
int main( int argc, char **argv )
{
hid_device * handle;
char value_str[8];
int result;
result = hid_init();
If initialization of HIDAPI was successful, we will now
attempt to open the connected USB device that matches
our vendor and product ID.
handle = hid_open( VENDOR_ID, PRODUCT_ID, NULL );
if ( handle == NULL )
{
wprintf( L"Unable to open ADU device with product ID %i.
Please check that it is connected. If it is connected, ensure you are running the example
with root privileges such as with sudo.\n", PRODUCT_ID );
result = hid_exit();
exit( -1 );
}
Now that we have successfully opened our device, we can
write commands to the ADU device and read the result.
Two convenience functions have been written to help
properly format command packets to send to the ADU
device, as well as to read a result from the ADU device:
write_to_adu() and read_from_adu(). We'll cover the
internals of these functions at the end of this page.
result = write_to_adu( handle, "RK0" );
result = write_to_adu( handle, "SK0" );
In order to read from the ADU device, we can send a
command that requests a return value (as defined in our
product documentation). Such a command for the ADU200 is
RPA. This requests the value of PORT A in binary.
read_from_adu() takes four parameters, the device
object, a destination buffer to store the string result,
the length of this buffer (must be 8 bytes or larger),
and a timeout in milliseconds.
result = write_to_adu( handle, "RPA" );
if ( -1 != read_from_adu( handle, value_str, 8, 200 ) )
{
wprintf( L"Read value as string: %s\n", value_str );
int value_int = atoi( value_str );
wprintf( L"Read value as int: %i\n", value_int );
}
Since we are finished with the device, we should close the device and shutdown the HIDAPI library.
hid_close( handle );
result = hid_exit();
return 0;
}
Further Details
When newly connecting to a device, we should also read
until there is nothing left to read from the device.
This is to clear any pending reads that was not
initiated by us:
while ( 0 != read_from_adu( handle, &value, 200 ) )
{
}
If you're interested in the internals of write_to_adu()
and read_from_adu() as well as how to structure a
command packet, the details are below.
All ADU commands have their first byte set to 0x01 and
the following bytes contain the ASCII representation of
the command. The ADU command packet format is described
here:
https://www.ontrak.net/Nodll.htm. As described in
the link, the remaining bytes in the command buffer must
be null padded (0x00). We do this via a memset.
We'll use hid_write() to write our command to the
device. After sending, we check to result to make sure
the transfer succeeded. The number of bytes sent should
match our command buffer's size (8 bytes)
int write_to_adu( hid_device * _handle, const char * _cmd )
{
const int command_len = strlen( _cmd );
unsigned char buffer[ COMMAND_SIZE ];
int bytes_sent;
if ( command_len > COMMAND_SIZE - 1 )
{
printf( "Error: command is larger than our limit of %i\n", COMMAND_SIZE - 1 );
return -1;
}
memset( buffer, 0, COMMAND_SIZE );
buffer[ 0 ] = 0x01;
memcpy( &buffer[ 1 ], _cmd, command_len );
bytes_sent = hid_write( _handle, buffer, COMMAND_SIZE );
if ( bytes_sent == -1 )
{
wprintf( L"Error writing to device: %s\n", hid_error( _handle ) );
}
return bytes_sent;
}
If the interrupt transfer succeeds, we should now have a
result to read from the command we sent. We can use
hid_read_timeout() to read the value. The parameters are
the same as for sending, plus a timeout value. The
buffer in this case will contain the data read from the
device and bytes_read will contain the number of bytes
that were read and stored in the buffer.
If reading from the device was successful, let's extract
the data we are interested in. It's important to note
that the data returned is 8 bytes in length. The first
byte of the data returned is 0x01 and is followed by an
ASCII representation of the number. The remainder of the
bytes are padded with 0x00 (NULL) values.
NULL) values.
int read_from_adu( hid_device * _handle, char * _read_str, int _read_str_len, int _timeout )
{
if ( _read_str == NULL || _read_str_len < 8 )
{
return -1;
}
int bytes_read;
unsigned char buffer[ COMMAND_SIZE ];
memset( buffer, 0, COMMAND_SIZE );
bytes_read = hid_read_timeout( _handle, buffer, COMMAND_SIZE, _timeout );
if ( bytes_read == -1 )
{
wprintf( L"Error reading interrupt transfer: %s\n", hid_error( _handle ) );
}
_read_str[7] = '\0';
memcpy( _read_str, &buffer[1], 7 );
return bytes_read;
}
This example illustrates the basics of reading and
writing to ADU devices using the HIDAPI library.
NOTE: When
running the example, it must be run with root privileges
in order to access the USB device. To compile the
makefile for Linux or OS X use:
make -f Makefile-linux
or make -f
Makefile-osx.
DOWNLOAD C HIDAPI Example (Linux and OS X)
|