Wednesday, September 11, 2013

Fun with USB

So I wanted to get rid of the USB-MIDI cable needed to run my Tesla coils and save 5 bucks...

Actually, I wanted to save as much as possible, and keep things through-hole. Fortunately, there exists a library called V-USB that implements a software USB stack on Atmel microcontrollers.

As usual, it starts with a board. Fortunately the actual hardware design was pretty trivial - the board consists of an ATTiny85, a micro-USB jack (mini-USB is so 2005), an output connector, and a couple of passives. The design is mostly based on this fellow's work here. I didn't want the diodes dropping the voltage down to 3.6V, so I left them out in the initial design. The boards were sent out to OSH Park since I was feeling cheap and didn't want to send out for a $100 Myro order, and a couple weeks later I had them on hand.

After putting a board together and plugging it in, I was immediately greeted with a very warm, very sad microcontroller. Hmmm...
So it turned out I had entered the micro-USB jack backwards into Eagle...sadly the fix was not as simple as making a reversed cable since D- had been connected to NC on the board, and naturally, NC was not connected in the cable.
After some fiddling around, I gave up and soldered a cable to the bottom of the board.
Next up was putting some firmware on the board. I flashed this demo using Atmel studio, upon which Windows angrily told me that the USB device had failed. Turns out those diodes were necessary.
A couple of 1N4007's later (the next revision of the board clamps D+ and D- with 3.3V zeners), I had a device which properly identified itself as a MIDI device. I felt extremely accomplished until I realized my MIDI device did absolutely nothing. So ended the first day's work.

The next day...
The next step was compiling the source provided in the demo. Sadly this was easier said than done. For starters, Atmel studio threw a bunch of syntax errors trying to compile V-USB in its stock state. Inserting 'const' into the appropriate places remedied that, and produced output that was neither the same size as the original binary, nor functional.
Next up was Eclipse, which didn't throw syntax errors, but produced similarly dysfunctional binaries.
Finally, I gave up and switched to a Makefile and Vim. This still didn't work, until I realized that V-USB depended on having an accurate value defined for F_CPU (which in this case is 16500000). At this point I was able to compile the demo file, flash it, and have the device show up as a MIDI device that did nothing.

Next: implement MIDI handling. Fortunately, USB-MIDI is not as complex as normal MIDI (for one thing, there is no running status). This document describes the packet structure nicely. A dozen lines of code and an ISR later, I had the board buzzing at 400Hz on every note.

From here it was presumably a matter of calculating 220*2^((n-57)/12) for each note received and setting some prescalers, right? Nope, including math.h to get a pow() function caused the microcontroller to run out of memory. The workaround was to precompute 2^((n-57)/12) for n=36, 37,...,47 and then use the fact that f(n+12)=2*f(n) to find the actual frequencies (so this way our LUT is only 12 entries instead of 128 entries long).

Finally I had the thing producing different tones. Unfortunately they were the wrong tones, and even more alarmingly, the interrupter would sometimes cause Windows to crash. The latter was attributed to the blocking nature of my ISR (adding ISR_NOBLOCK fixed it), but the former was trickier to track down.
After finally pulling out the scope (scopes are better test equipment than ears!) I realized all of my on-times were 600uS, which threw off the frequencies of the high notes pretty spectacularly. It turns out that _delay_us() and _delay_ms() only take constant values as arguments, so I switched to _delay_loop_2() and it worked just fine.

The last bits to implement were master volume control (control change 0x07) for power control, limiting the notes played to channel 1, and STOP messages (which are harder to implement than you'd think; there are six ways to stop a MIDI track and it's easy to forget one...). The results are still a bit too unstable to post (I need to work out some host-side issues involving driver installation) but at this point I'm pretty sure I have (what might be the first?) direct-USB interrupter. It's not really good for DRSSTC use (for one thing it's copper out, not fiber out) and is pretty limited in what it can do (no hardware knobs, only one note at a time though this might change tomorrow) but it's dirt cheap (the total cost of hardware is something like $3) and should work acceptably for a small SSTC.

Speaking of which...well, hopefully that project will come together in the next day or two.