USBHID C bindings
USBHID C/C++ interface for mbed-based HID devices¶
There appear to exist two libraries that claim to support cross-platform HID device development for C/C++:
- HID API (tested on Linux, Windows and Mac OS X - shouldn't need a Windows driver)
- libhid (tested on Linux only, not in Ubuntu packages anymore - needs the libusb driver)
Both will be discussed in this cookbook entry.
mbed code¶
We'll run the USBHID_TestCase program on the mbed, to check that the bindings work, as suggested on the USBHID bindings cookbook page :)
Import programUSBHID_TestCase
USBHID test case
This will simply send some random data to the PC and display any received data through the USB serial port.
I recommend clicking on the m0-beta and the USBDevice libraries and updating them to the latest version, using the button on the right hand side.
This is common to both libraries.
mbed wiring¶
We'll be using an "external" USB connector, connected to D+, D-, GND and optionally Vin, as shown on the USBDevice page. The USB cable connected to your PC is then plugged into this connector.
HID API¶
First, download the library: go to hidapi @ github and either grab the git clone command (git clone https://github.com/signal11/hidapi.git
), or download the zip archive if you don't have git installed.
Linux / Mac OS X (using gcc/g++)¶
- Navigate to the hidtest folder inside the main hidapi folder. Replace the contents of
hidtest.cpp
with the code below. - Navigate to the
linux
ormac
folder under the main hidapi folder, and runmake
. - Run
./hidtest
(on Linux, you'll need sudo, unless you've setup udev rules).
Windows (using Visual Studio)¶
- Open the hidapi.sln file in the windows subdirectory of hidapi. Depending on which version of Visual Studio you have,the conversion wizard may pop up, just follow the instructions it gives you.
- In the solution explorer on the right, open hidtest.cpp (it's under hidtest->source files)
- Replace the contents with the code below.
- Right-click hidtest in the solution explorer, and choose "Set as StartUp project" (that way, you can use the green "play" arrow to launch the binary)
- Build the solution (Build->Build Solution)
- Run it by clicking on the green "play" arrow, or choose "Start debugging" from the Debug menu.
sample code¶
The code below is based on HID API's hidtest.cpp sample, it's just been changed slightly so that it works with the mbed code linked above.
hidtest.cpp
/******************************************************* Windows HID simplification Alan Ott Signal 11 Software 8/22/2009 Copyright 2009, All Rights Reserved. This contents of this file may be used by anyone for any reason without any conditions and may be used as a starting point for your own applications which use HIDAPI. ********************************************************/ #include <stdio.h> #include <wchar.h> #include <string.h> #include <stdlib.h> #include "hidapi.h" // Headers needed for sleeping. #ifdef _WIN32 #include <windows.h> #else #include <unistd.h> #endif int main(int argc, char* argv[]) { int res; unsigned char buf[9];// 1 extra byte for the report ID #define MAX_STR 255 wchar_t wstr[MAX_STR]; hid_device *handle; int i; #ifdef WIN32 UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); #endif struct hid_device_info *devs, *cur_dev; devs = hid_enumerate(0x0, 0x0); cur_dev = devs; while (cur_dev) { printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number); printf("\n"); printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string); printf(" Product: %ls\n", cur_dev->product_string); printf(" Release: %hx\n", cur_dev->release_number); printf(" Interface: %d\n", cur_dev->interface_number); printf("\n"); cur_dev = cur_dev->next; } hid_free_enumeration(devs); // Set up the command buffer. memset(buf,0x00,sizeof(buf)); buf[0] = 0x01; buf[1] = 0x81; // Open the device using the VID, PID, // and optionally the Serial number. ////handle = hid_open(0x4d8, 0x3f, L"12345"); handle = hid_open(0x1234, 0x6, NULL); if (!handle) { printf("unable to open device\n"); return 1; } // Read the Manufacturer String wstr[0] = 0x0000; res = hid_get_manufacturer_string(handle, wstr, MAX_STR); if (res < 0) printf("Unable to read manufacturer string\n"); printf("Manufacturer String: %ls\n", wstr); // Read the Product String wstr[0] = 0x0000; res = hid_get_product_string(handle, wstr, MAX_STR); if (res < 0) printf("Unable to read product string\n"); printf("Product String: %ls\n", wstr); // Read the Serial Number String wstr[0] = 0x0000; res = hid_get_serial_number_string(handle, wstr, MAX_STR); if (res < 0) printf("Unable to read serial number string\n"); printf("Serial Number String: (%d) %ls", wstr[0], wstr); printf("\n"); // Read Indexed String 1 wstr[0] = 0x0000; res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); if (res < 0) printf("Unable to read indexed string 1\n"); printf("Indexed String 1: %ls\n", wstr); // Set the hid_read() function to be non-blocking. hid_set_nonblocking(handle, 1); // send and receive 20 reports for (int r = 0; r < 20; r++) { // send some dummy data buf[0] = 0x0; //report number for (i = 1; i < sizeof(buf); i++) { buf[i] = r + i; } res = hid_write(handle, buf, sizeof(buf)); if (res < 0) { printf("Unable to write()\n"); printf("Error: %ls\n", hid_error(handle)); } // Read requested state. hid_read() has been set to be // non-blocking by the call to hid_set_nonblocking() above. // This loop demonstrates the non-blocking nature of hid_read(). res = 0; while (res == 0) { res = hid_read(handle, buf, sizeof(buf)); if (res == 0) printf("waiting...\n"); if (res < 0) printf("Unable to read()\n"); #ifdef WIN32 Sleep(500); #else usleep(500*1000); #endif } printf("Data read:\n "); // Print out the returned buffer. for (i = 0; i < res; i++) printf("%02hhx ", buf[i]); printf("\n"); }//end 20 reports //close HID device hid_close(handle); /* Free static HIDAPI objects. */ hid_exit(); #ifdef WIN32 system("pause"); #endif return 0; }
Result¶
In the left hand terminal, you can see the above code executing; the right hand one shows the serial output coming from the mbed's normal (USBTX, USBRX) serial port.
And it even works on Windows!
And Mac OS X!
Troubleshooting¶
- Make sure that the first byte of your report is 0x0 (unless you know what you're doing), and that you're "sending" 1 more byte than the mbed expects. On Windows, I got the following errors when this was not the case:
- when writing for the first time:
Error: The supplied user buffer is not valid for the requested operation
- all subsequent writes:
Error: The parameter is incorrect
- This error appears to only come up on Windows; see Issue #13 on the hidapi github page. I still think it's best to get the report size right though, as under Linux you'll miss off a byte :)
- when writing for the first time:
- I'm assuming the mbed's vendor ID is 0x1234 and product id is 0x6.
libhid¶
This may not be the best way to get things done, since it appears that the package isn't maintained anymore, and may have some bugs. At least that was the reason I found for it's removal from the ubuntu repositories...
It's distributed under the GPL v2 licence.
PC-side code¶
- Make sure you've got libusb installed. It should be in the package manager of most linux distributions, and there is a windows port as well.
- Install libhid. There used to exist ubuntu packages for this, and Arch Linux has an AUR package for it. Otherwise, you can always go the ./configure, make, make install route.
- Copy/paste the program below into a file; it's based on libhid's test_libhid.c.
- Compile it:
gcc -o test_libhid test_libhid.c -lhid
- Run it; you may need sudo, depending on your udev rule setup.
#include <hid.h> #include <stdio.h> #include <string.h> #include <unistd.h> /* for getopt() */ bool match_serial_number(struct usb_dev_handle* usbdev, void* custom, unsigned int len) { bool ret; char* buffer = (char*)malloc(len); usb_get_string_simple(usbdev, usb_device(usbdev)->descriptor.iSerialNumber, buffer, len); ret = strncmp(buffer, (char*)custom, len) == 0; free(buffer); return ret; } int main(int argc, char *argv[]) { HIDInterface* hid; int iface_num = 0; hid_return ret; unsigned short vendor_id = 0x1234; unsigned short product_id = 0x6; char *vendor, *product; int flag; /* Parse command-line options. * * Currently, we only accept the "-d" flag, which works like "lsusb", and the * "-i" flag to select the interface (default 0). The syntax is one of the * following: * * $ test_libhid -d 1234: * $ test_libhid -d :5678 * $ test_libhid -d 1234:5678 * * Product and vendor IDs are assumed to be in hexadecimal. * * TODO: error checking and reporting. */ while((flag = getopt(argc, argv, "d:i:")) != -1) { switch (flag) { case 'd': product = optarg; vendor = strsep(&product, ":"); if(vendor && *vendor) { vendor_id = strtol(vendor, NULL, 16); } if(product && *product) { product_id = strtol(product, NULL, 16); } break; case 'i': iface_num = atoi(optarg); break; } } /* How to use a custom matcher function: * * The third member of the HIDInterfaceMatcher is a function pointer, and * the forth member will be passed to it on invocation, together with the * USB device handle. The fifth member holds the length of the buffer * passed. See above. This can be used to do custom selection e.g. if you * have multiple identical devices which differ in the serial number. * * char const* const serial = "01518"; * HIDInterfaceMatcher matcher = { * 0x06c2, // vendor ID * 0x0038, // product ID * match_serial_number, // custom matcher function pointer * (void*)serial, // custom matching data * strlen(serial)+1 // length of custom data * }; * * If you do not want to use this, set the third member to NULL. * Then the match will only be on vendor and product ID. */ // HIDInterfaceMatcher matcher = { 0x0925, 0x1237, NULL, NULL, 0 }; HIDInterfaceMatcher matcher = { vendor_id, product_id, NULL, NULL, 0 }; /* see include/debug.h for possible values */ //hid_set_debug(HID_DEBUG_ALL); hid_set_debug_stream(stderr); /* passed directly to libusb */ hid_set_usb_debug(0); ret = hid_init(); if (ret != HID_RET_SUCCESS) { fprintf(stderr, "hid_init failed with return code %d\n", ret); return 1; } hid = hid_new_HIDInterface(); if (hid == 0) { fprintf(stderr, "hid_new_HIDInterface() failed, out of memory?\n"); return 1; } /* How to detach a device from the kernel HID driver: * * The hid.o or usbhid.ko kernel modules claim a HID device on insertion, * usually. To be able to use it with libhid, you need to blacklist the * device (which requires a kernel recompilation), or simply tell libhid to * detach it for you. hid_open just opens the device, hid_force_open will * try n times to detach the device before failing. * In the following, n == 3. * * To open the HID, you need permission to the file in the /proc usbfs * (which must be mounted -- most distros do that by default): * mount -t usbfs none /proc/bus/usb * You can use hotplug to automatically give permissions to the device on * connection. Please see * http://cvs.ailab.ch/cgi-bin/viewcvs.cgi/external/libphidgets/hotplug/ * for an example. Try NOT to work as root! */ ret = hid_force_open(hid, iface_num, &matcher, 3); if (ret != HID_RET_SUCCESS) { fprintf(stderr, "hid_force_open failed with return code %d\n", ret); return 1; } ret = hid_write_identification(stdout, hid); if (ret != HID_RET_SUCCESS) { fprintf(stderr, "hid_write_identification failed with return code %d\n", ret); return 1; } ret = hid_dump_tree(stdout, hid); if (ret != HID_RET_SUCCESS) { fprintf(stderr, "hid_dump_tree failed with return code %d\n", ret); return 1; } /* How to write to and read from a device: * * The original code uses a HID usage path, whereas I use interrupt * endpoints, since that was what I could get working. * Feel free to take a look at the original code for the alternative * way of reading/writing: * http://anonscm.debian.org/viewvc/libhid/trunk/test/test_libhid.c?view=markup * * * My approach was to essentially execute * lsusb -vvv -d 1234:0006 * where the last argument consists of <vendor ID>:<product ID> * Now, look under "Endpoint Descriptor:" for: * bEndpointAddress 0x81 EP 1 IN * bEndpointAddress 0x01 EP 1 OUT * if the hexadecimal values of yours are different to mine, * edit the 0x1 and 0x81 in hid_interrupt_read and _hid_interrupt_write, * respectively to match your values. * * The following API doc might be helpful: * http://libhid.alioth.debian.org/doc/hid_8h.html */ //prepare a packet unsigned const int packet_len = 8; //bytes char buf[packet_len]; memset(buf, 0, sizeof(buf)); int i, p; //receive & send 20 packets. for (p = 0; p < 20; p++) { //read ret = hid_interrupt_read(hid, 0x1, buf, 8, 1000); if (ret == HID_RET_SUCCESS) { printf("recv: "); for (i = 0; i < sizeof(buf)/sizeof(*buf); i++) { printf("%d ", buf[i]); } printf ("\n"); } else { fprintf(stderr, "hid_interrupt_read failed with code %d\n", ret); } //write printf("send: "); for (i = 0; i < sizeof(buf)/sizeof(*buf); i++) { buf[i] = i * i; printf("%d ", buf[i]); } printf("\n"); ret = hid_interrupt_write(hid, 0x81, buf, 8, 1000); if (ret != HID_RET_SUCCESS) { fprintf(stderr, "hid_interrupt_write failed with code %d\n", ret); } } //close ret = hid_close(hid); if (ret != HID_RET_SUCCESS) { fprintf(stderr, "hid_close failed with return code %d\n", ret); return 1; } hid_delete_HIDInterface(&hid); ret = hid_cleanup(); if (ret != HID_RET_SUCCESS) { fprintf(stderr, "hid_cleanup failed with return code %d\n", ret); return 1; } return 0; } /* COPYRIGHT -- * * This file is part of libhid, a user-space HID access library. * libhid is (c) 2003-2005 * Martin F. Krafft <libhid@pobox.madduck.net> * Charles Lepple <clepple@ghz.cc> * Arnaud Quette <arnaud.quette@free.fr> && <arnaud.quette@mgeups.com> * and distributed under the terms of the GNU General Public License. * See the file ./COPYING in the source distribution for more information. * * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES * OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */
Result¶
In the left hand terminal, you can see the above code executing; the right hand one shows the serial output coming from the mbed's normal (USBTX, USBRX) serial port.
Troubleshooting¶
- try uncommenting the line
//hid_set_debug(HID_DEBUG_ALL);
to get some more debug output. - If you're getting Input/Output errors, maybe your endpoint addresses are different. Have a look at the comment in the code before hid_interrupt_read and hid_interrupt_write to see how I got mine.
- I'm assuming the mbed's vendor ID is 0x1234 and product id is 0x6.