picture home | pixelblog | qt_tools

omino code blog

We need code. Lots of code.
David Van Brink // Sun 2008.03.23 20:54 // {c code wbl weird blinking lights}

Retro USB MIDI Device VI

First Working Code

After much tinkering and poring over data dumps and specs, I have flashed the PICDEM FS USB board such that, when plugged into the Mac, it shows up as a usable MIDI input. (Doesn’t yet show up properly on Windows XP, though.) This, using a Pic 18F4550.

From reading and googling the subject, it seems that most USB developers start by creating the descriptors, and committing them to ROM.

I took a different approach. I wanted to have a flexible and repeatable way to create new MIDI devices as needed. Who knows, I might do more than one. And this way others might benefit from it too, potentially. As part of main, I call a setup method which dynamically creates the all the descriptors, strings, & so on. I donated 256 bytes of RAM (out of 2k) to hold the descriptors. And the code to poke it all in chews up a K or so of program space. But, for now at least, the freewheeling flexibility is worth it.

        "omino.com products",
        "omino 8038 controller"

The above C call creates in memory the device and configuration descriptors for a Streaming Audio (MIDI) USB device with Vendor ID 0832, Product ID 401C, five MIDI input cables, five MIDI output cables, for a company named “omino.com products”. It shows up in the MIDI patchbay as an “omino 8038 controller”.

Below the cut, some trivia about the Pic 18F4550, the PICDEM FS USB demo board, and their development tools.


MCC18. Microchip’s C compiler, MCC18, has a few quirks and extensions. Data may be in program memory (ROM) or general purpose registers (RAM). These are separate address spaces; to access program memory, the variable must be marked rom

The Pic 18-family instruction set is much more compact for accessing absolute RAM locations than stack-offsets. To leverage this, variables may be marked overlay, which is almost like statics or globals, except that the fixed location might be shared among multiple routines that don’t call each other. Obviously, this can’t be used if recursion happens.

void copyRomStrToRam(const rom char *src,char *dst,unsigned char len)
	overlay unsigned char i;
	for(i = 0; i < len; i++)
		dst[i] = src[i];

Interrupt Vectors on the 18F4550. Their examples have interrupt vectors at 0x808 and 0x818. I tried to emulate that, but things kept failing. The interrupt vectors are really at 0x8 and 0x18... but the examples all assume you've installed their boot loader. The boot loader is a super-reduced USB stack that fits in 2K, 0-0x800, and lets you reflash the ROM over USB. Handy, but I wasn't using it, didn't have it installed. Now I poke the isr vector jumps in both locations, superstitiously, and all is well.

#pragma code _RESET_INTERRUPT_VECTOR = 0x000800
void _reset (void) { _asm goto _startup _endasm }

#pragma code _HIGH_INTERRUPT_VECTOR = 0x000808
void _high_ISR (void) { _asm goto isr _endasm }
#pragma code _HIGH_INTERRUPT_VECTOR_0 = 0x000008
void _high_ISR_0 (void) { _asm goto isr _endasm }

#pragma code _LOW_INTERRUPT_VECTOR = 0x000818
void _low_ISR (void) { _asm goto isr _endasm }
#pragma code _LOW_INTERRUPT_VECTOR_0 = 0x000018
void _low_ISR_0 (void) { _asm goto isr _endasm }

Mac OS X and USB Stickiness. While iterating on the design, it seemed like my changes to the device would be ignored. I'd flash a new program, reset the device, and watch it show up again, reenumerated, on the Mac. But it didn't change... Turns out if it has the same product and vendor ID, the Mac remembers what it was from last time. Workaround: bump the product ID each time. (By the way, I'm using made-up vendor and product IDs just now.)

Convenient Timer Numbers. The typical clock setup with the 18F4550, the one on the demo board, is a 20MHz crystal divided on-chip down to 4Mhz, and back up to 48MHz and 96MHz. This (it turns out) gives the CPU 12 Mips. I used Timer 0 to divide this by 46875, yielding a tickrate of exactly 256Hz. This is so handy; each tick, I increment a 16-bit word. The low byte is 256ths -- good enough for musical timing -- and the high byte is seconds. Might go a bit higher (look at all those 5's that divide out from 46875) and use the IRQ to do LED brightness, as well. 12 Mips seems infinite.

Which Ports Are Available. What a puzzle. There are 5 8-bit ports, PORTA throught PORTE. But not every port has all 8 bits implemented. And some bits are preallocated to certain functions, like USB. Others happen to be used on this board for various things. And others... don't happen to show up on the easy connector on the demo board.

For reference, here's the ones I'm using. RC6 and RC7 (after cutting jumpers JP3 through JP6 on the back, since I don't intend to use the RS232 port, though they could be easily reconnected). And RC1, RC2, RA4, and RA5.

Using for what? Ah. Some pictures of a prototype MIDI controller, next up.

oh, i dont know. what do you think?

(c) 2003-2011 omino.com / contact poly@omino.com