Learning To Speak HID
Okay. I have a display, I have lights, and I have buttons. In other words, the hardware is complete, with one exception: connectivity. All I need now is to get this thing talking to the Mac and I’ll be in good shape.
Easier said than done.
I should take this moment to note that I’m now seriously considering AVR for my next project, simply because their development tools are so much better (gcc FTW!). Hi-Tech PICC18 is about the best thing available for free (at least that I could find), and it’s pretty bad. Felt like I was working with an extremely buggy version of the ancient Borland Turbo C.
To give you a simple example, at one point I managed to crash the compiler simply by having an unused variable at the top of a function. And we won’t even talk about MPLAB C18; that made Hi-Tech’s stuff look positively modern! Color me disappointed; this compiler was acceptable ten years ago, but today? I expected far better.
Rumor has it that PIC24 and PIC32 use a gcc derivative; maybe I’ll try those next time, but AVR is really starting to look attractive for a lot of reasons.
I also have a few issues with Microchip’s application “library.” Note the quotes; the thing isn’t actually a library. It’s a collection of hackable sample code; nothing more, and nothing less. Now I’m even more disappointed…
…and that’s before we get to the fact that the USB sample won’t compile out of the box with Hi-Tech C. Are you getting the idea yet? Getting the USB module to work was an exercise in frustration not because the concepts were hard, but because the tools they give you are rife with annoying issues before you ever get to write a single line of code.
Many hours later, I finally come across someone who ported the MAL USB stack over to Hi-Tech C (which I was way too lazy to do; remember when this was supposed to be a quick hack?). Thus I enter the murky world of the most overly-complicated protocol I’ve ever had the pleasure of tinkering with. USB itself is relatively simple, but HID is a whole different story.
To cut that long story short: HID has descriptors, which you can use to make it much simpler for applications (even ones you didn’t write) to make sense of your nifty new hardware device. I initially started going down this path, writing descriptors and everything, but quickly discovered that it’s a lot more work than I was interested in doing. Instead, I took a shortcut.
My HID descriptor defines a report that contains a bunch of opaque data bytes.
After figuring out how much easier that was going to be, it was a trivial matter just to define a binary structure and pass it around. I’d eventually like to go back and do it right, but that’ll wait until I have final hardware and some time to kill. Might be a while.
On the good side, while it’s a bit slow when compared to what USB is capable of, HID has a single major advantage for this project: it doesn’t require drivers on the Mac (or Windows, or Linux). Dealing with HID devices is accomplished almost entirely within the controlling application, which is perfect for what I’m building. And it turns out that it’s not so difficult to access a HID device from a Cocoa app, though it unfortunately doesn’t have an Objective-C interface just yet.
Getting this to work was amazingly simple, all things considered; it only took a few hours. Far more time than that was spent dealing with the development tools themselves. In the end my firmware understood a very simple set of commands, and could send a couple of its own:
|GET_INFO||Host||Device||Requests that a settings packet be sent as soon as possible.|
|SET_LEDS||Host||Device||Sets the state of the LEDs (duh!). Options are On, Off, or Blink.|
|SET_TITLE||Host||Device||Sets the content of the first LCD display line, up to 40 characters in length. If the length is longer than 16 characters, the firmware will “Larson scroll” it, so to speak.|
|SET_ARTIST||Host||Device||Same as above but for the second line of the display. Lines will scroll independently if needed.|
|SETTINGS||Host||Device||Set the display brightness and contrast (so far, possibly more later).|
|Device||Host||Report the firmware revision and brightness/contrast settings.|
|KEYPRESS||Device||Host||Identifies a button that was just pressed.|
|WAKE||Device||Host||We just woke up from a USB suspend. Please reinitialize us!|
Pretty easy, right? I’ll probably rename the Artist/Title packets to Line1/Line2 later, but it won’t change the functionality. Might also later expand KEYPRESS to include keyup/keydown states instead of one-shot key hits, but for now…
It works! The best part, though: the USB hardware is virtually non-existent. By this time my crystals had arrived, so I plopped an 8MHz crystal in the circuit, changed the settings to run it at 24MHz internally, and off I went. I had to adjust all the internal timing as a result of the clock change, but oh well.
The only hardware involved beyond the crystal was a cannibalized USB cable with header pins soldered to the ends of the wires.