A while ago I picked up an LPCXpresso board (specifically the LPC11U14 one), because it was going cheap on end-of-life runout sale, and it has USB built-in. I have a bad habit of buying more dev boards than I get around to ever using, so I was determined to actually use this one.
I decided to build some “multimedia keys” for my PC. The essential idea is to replicate the on-screen controls of a standard music player: volume, play/pause, next track, previous track, mute. My keyboard already has these, but they’re horrible touch-sensitive spots with no tactile feedback, and I always hit the wrong button (usually hitting the one that turns off the keyboard’s illumination, which leaves me wildly poking around blind until I turn it back on).
Another, slightly more uncommon, feature I wanted was a display of the current volume level. I figured an LED bargraph would be a good way to do this. I don’t need the precision of a number (32% volume vs 31% … who cares), so a bargraph with 5-10 increments would be the perfect tool for the job.
OK, requirements are (loosely) defined, how to program this thing? The board has a programmer built-in (it uses another, vastly more powerful LPC chip), so it’s just a matter of downloading the IDE (Eclipse-based), some example code, and finding a couple of mini-USB cables (hey NXP, use micro-USB on the next revision please!).
There are a couple of USB HID examples – a mouse, a keyboard, and a “composite” device comprised of a mouse and virtual serial port. I decide that the virtual serial port would be nice for debugging… because I have no wires to use for testing within arms reach. That’s right, I write a very basic interactive shell rather than look for a wire and soldering iron. Getting the virtual serial port working is actually not quite as dumb as it sounds, because it lets me spew out diagnostic data too.
I figured it would be easy to take the “composite” project, cut out the mouse parts, and paste in the keyboard parts. Unfortunately it wasn’t, mainly because I didn’t know jack about USB descriptors, but I got there in the end.
Some experiments with trying to get the chip to send an absolute volume signal (e.g. “hey computer, you got to 50% now”) were ultimately abandoned. For one, it would create race conditions between the setting on my USB device, and the setting on the PC (imagine the knob on the device is at 50%, user changes PC to 30%, now turns the volume knob down a bit… volume changes to 49%?). For two, I couldn’t bloody figure out how to make it work. At length I eventually added a linear analog control to the HID descriptor, but then gave up on the PC driver side (the multimedia keys work with the default OS drivers).
I also fought with HID descriptors for a while over adding the volume indication. I thought the “proper” way to do it would be to add an analog indicator (*check USB terminology*) to my descriptor, and get the driver to periodically send the system’s current volume reading…
But, well, I already had the virtual serial port working, and didn’t have a driver. So I just deleted the interactive debug shell and make the serial port accept 1 byte at a time, driving a bar graph appropriately (255 = full bar graph, 0 = empty). A convoluted bash command later, and the bar graph now correlates with my system volume:
amixer get Master | grep –only-matching [[:digit:]]*% | grep –only-matching [[:digit:]]* | ./percent2hex.py > /dev/ttyACM0
pc = sys.stdin.readline()
pc = int(pc)
pc = min(100,max(0,pc))
It seems sad, looking back, that what can be described in a few paragraphs took me so many hours, but such is the price of sharpening rusty skills. I did have a steep learning curve – new chip, new IDE, never coded USB before, didn’t know jack about USB HID. Now I can at least say I have “experience” in these areas.
- use a rotary encoder instead of buttons for up/down
- design a stand-alone PCB & case
I’ll try to come back later with some source code. If you’re reading this, I obviously haven’t – drop me a message!