Microchip’s PIC18 USB HID Demo host software for linux using c/c++ and hiddev (no external libraries)

Nowadays,  developing  embedded full speed USB applications is quite easy and inexpensive using Microchip’s USB Framework for PIC18, PIC24 and PIC32 since extensive examples are provided by the company for us to easily tweak. Nevertheless these examples are primarily thought to be used under windows and using Microchip development boards, so using your own custom hardware and/or working under a different OS (i.e. linux) can get somewhat cumbersome.

Last year, for my bachelor degree thesis I had to develop a USB interface to connect some analog sensors to my PC ,  so an HID class device based on the corresponding Microchip’s example sounded just perfect. However, since I was using custom hardware and had to make it work under Ubuntu 13, some extra effort was required. Plus, due to some requirement constraints I wasn’t able to use external libraries such as libusb so I spent several months trying to make it work.

This [incomplete] guide is intended to summarize my experiences with this project. Be aware that USB protocol is NOT EASY to understand or implement. Some proficiency in c/c++ as well as basic understanding of USB protocol concepts, embedded programming, digital electronics and linux file systems are assumed.

I’m using gcc/g++ compilers for the host code, MPLAB IDE (you can get the free version) and C18 for the firmware, Ubuntu 13.10 as OS and a PIC18F4550 microcontroller. Other software/hardware combinations may work but you’re on your own.

DISCLAIMER: I’m no expert on USB communication or electronics whatsoever, so NO WARRANTIES of any kind.

Hardware/Firmware

[If you actually have a Microchip USB development board or  somehow you managed to get Microchip’s USB HID example running on your microcontroller you may skip to the next part  ]

These are the schematics for the microcontroller’s circuit I’m using:

Schematics for the USB HID example
Schematics for the USB HID example

NOTE: You’re supposed to use a 20MHz crystal, but it is possible to use other as long as it’s at least 4Mhz (that’s the one I’m using) but you’ll have to make some modifications to the firmware’s code (keep reading, they’re detailed a bit later)

For the firmware, the first step is to download Microchip’s usb framework (this is the hompage http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2680&dDocName=en537044).

Look into the demos folder for an MPLAB project called “USB Device – HID – Simple Custom Demo – C18 – PICDEM FSUSB.mcp” and open it. This is the firmware for PIC18F4550-based boards. If your using a development board or custom hardware with a 20Hz crystal just build the project and program your hardware.

In case you’re using a different crystal, open main.c and look for the line that reads:

#if defined(PICDEM_FS_USB)

There you can modify the configuration bits for your PIC. For a 4Mhz crystal,  your code should look something like this:

#if defined(PICDEM_FS_USB) // Configuration bits for PICDEM FS USB Demo Board (based on PIC18F4550)
 #pragma config PLLDIV = 1 //No divide, 4MHz input
 #pragma config CPUDIV = OSC1_PLL2 //[OSC1/OSC2 Src: /1][96MHz PLL Src: /2]
 #pragma config USBDIV = 2 // Clock source from 96MHz PLL/2
 #pragma config FOSC = HSPLL_HS //HS oscillator, PLL enabled, HS used by usb
 #pragma config FCMEN = OFF //Fail safe clock monitor
 #pragma config IESO = OFF //Interal/external switch over
 #pragma config PWRT = OFF //Power up timer
 #pragma config BOR = ON //Brown out reset

Now let’s talk about the PC side…

Host software

[Most of the code for this is based on Antonio Maccioni’s work here http://openprog.altervista.org/USB_firm_eng.html ]

For the host side of the application I’m using the hiddev interface so you’ll need to include some header files:


#include <sys/ioctl.h>
#include 	<linux/hiddev.h>
#include <fcntl.h>

The first thing we’ll do is to look for the device. On Ubuntu (and other linux distributions), HID devices are enumerated under /dev/usb/hiddevX   (X = 1,2,3…). Open each of the existing HID devices, copy their info into a hiddev_devinfo struct and look for VID  = 0x04D8  and PID= 0x003F which are the vendor and product ID’s specified in the HID example. You can change them, just make sure that they match with the ones on the Firmware.


int InitializeUSB(int VID, int PID)
{
int i = 0;

printf("Searching Microchip (VID 0x%04X and PID 0x%04X)\n",VID, PID);
//search amongst the first MAX_DESCRIPTORS descriptors in "/dev/usb/hiddev%d"
// for the device's VID & PID
for( i ; i<MAX_DESCRIPTORS ;i++)
{
sprintf(devicePath,"/dev/usb/hiddev%d",i);
deviceDescriptor = open(devicePath,O_RDONLY);
if(deviceDescriptor >= 0)//if file was opened properly
{
ioctl(deviceDescriptor, HIDIOCGDEVINFO, &deviceInfo);
if(deviceInfo.vendor == VID && deviceInfo.product == PID)
{
printf("Device found: %s\n",devicePath);
break;
}
}
}

if(i >= MAX_DESCRIPTORS)
{
printw("Device NOT found!\n");
return -1;
}
}

We need a special kind of structures to perform read/write operations. For detailed information about them visit  http://www.wetlogic.net/hiddev/.

Initialize the structures according to the type of report.

//these structs hold information about the reports
struct hiddev_report_info inReportInfo;
struct hiddev_report_info outReportInfo;
//information about the endpoint usage
struct hiddev_usage_ref_multi inUsage;
struct hiddev_usage_ref_multi outUsage;

outReportInfo.report_type = HID_REPORT_TYPE_OUTPUT;
outReportInfo.report_id = HID_REPORT_ID_FIRST;
outReportInfo.num_fields = 1;

inReportInfo.report_type = HID_REPORT_TYPE_INPUT;
inReportInfo.report_id = HID_REPORT_ID_FIRST;
inReportInfo.num_fields = 1;

outUsage.uref.report_type = HID_REPORT_TYPE_OUTPUT;
outUsage.uref.report_id = HID_REPORT_ID_FIRST;
outUsage.uref.field_index = 0;
outUsage.uref.usage_index = 0;
outUsage.num_values = REPORT_SIZE;

inUsage.uref.report_type = HID_REPORT_TYPE_INPUT;
inUsage.uref.report_id = HID_REPORT_ID_FIRST;
inUsage.uref.field_index = 0;
inUsage.uref.usage_index = 0;
inUsage.num_values = REPORT_SIZE;

Actual read/write operations are done through ioctl calls. We’ll use a byte array (unsigned char) to send and receive the data buffer from the USB device. When writing, the first position (index 0) of the array must contain the report ID:

0x37 = read POT value
0x80 = toggle LED’s
0x81 = get button state

When reading, first data byte is at the second byte of the array (index 1). Now you need to process the raw bytes to get meaningful information:


//Get POT value
GetUSBData(buffer, 0x37);
POTValue = (float)((buffer[2]<<8) + buffer[1]);
POTValue=POTValue*100/1024; //get porcentage, max value is 1024

//toggle LED's
GetUSBData(buffer, 0x80);
//We don't care about the response

//Get button state
GetUSBData(buffer, 0x81);
if(buffer[1] == 0x01)
printf("Button is pressed ");
else
printf("Button is NOT pressed ");

Last, we close the device

close(deviceDescriptor);

Here   you can download the full code for the basic host software. Build it with gcc HIDDemo.c. Select the action you want from the menu using your keyboard.
If you’re looking for something fancier take a look at this threaded version of this program. Build with g++ -pthread  -std=c++11  HIDDemoThreaded.cpp -lncurses. You’ll need a c++11 compliant compiler and ncurses_dev package installed. While running press ‘t’ to toggle the LED’s or ‘q’ to quit. You can look at it working on this video.

Both codes are distributed under the GPL license.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s