Spring 2005: 594O, Media Interface Technology

Home | Concept | Body & instrument | Optical tonearm | Turntable platter | PIC code | Review


Close-up detail of CREATE USB Interface circuit & connectors
PIC code overview

The software on the PIC has 2 seperate modules, a bootloader for programming the chip through the USB port and the runtime software for driving physical media and streaming data to and from the computer. Initially, the chip is programmed with an ICSP (in circuit serial programmer) in order to load the bootloader on to the chip. Once the bootloader is stored in the PIC's flash memory, you can program the chip through the USB port using the bootloader software provided by Microchip. We used VirtualPC (Windows 2000) on OSX to do our PIC software development and programming.

The software on the chip is setup to enumerate as a Logitech Wingman forcefeedback device. Using the USB Prober application insalled with OSX developer tools, we copied the HID usage table from a real Wingman game controller and pasted it into the HID usage table for out PIC. Just pasting the HID table into our PIC code didn't immediately cause the computer to recognize the PIC as a Wingman controller however. We had to use a demo version of HHD Software's USB Monitor (http://www.hhdsoftware.com/usbmon.html) to sniff the USB packets of the real Wingman to figure out the differences between our PIC and the Wingman. In the end, we noticed that a hex '1' (0x01) is always sent in the first byte of the 8 byte buffer of data sent to the computer, so after doing this in our code, our PIC became a full fledged Wingman game controller.

The main reason for setting up our PIC as a Wingman had to do with the current state of forcefeedback on OSX and in Max/MSP. Coincidentally enough, as we were building our device, a rudimentary forcefeedback object for OSX was released based on the Immersion USB API. Logitech supports the Immersion API, so the new Max object could talk to Logitech forcefeedback controllers like the Wingman and our PIC.

The main loop in our code is called processIO(). This function is called as fast as possible in our current software design. It checks the status of the phyiscal interface through A/D converters and digital pins as well as checking the USB port to see if it is ready to receive or send new data. Currently, processIO() is called at a frequency of around 30KHz with a few KHz jitter due to unpadded branching in the function.

Using the oscilloscope, we discovered that the HIDTxReport() function (which sends the data from the PIC to the computer using the Human Interface Device protocol) is called at a rock-steady 125Hz (corresponding to 8.00ms). In order to filter out spurious high frequency noise in the platter speed IR circuit, we poll the value of the IR circuit pin on each ProcessIO, and return the new value if it remains latched across each 8 iterations (performing the action in the 9th). We can get a steady measurement of the rotation frequency by counting the number of transitions between a predetermined number of HIDTxReport calls.   We chose to count the transitions between every 16 iterations of the HIDTxReport(), which returned values less than 255 (the 8-bit rollover limit) for the fastest rotation speeds that seemed reasonable.   The update rate therefore is 128ms.

Code example: platter control (part of ProcessIO())
//not busy precisely every 8ms
	if(!mHIDTxIsBusy()) {
		//turn off motor after 32ms
		if(transmitCount == 4) {
			LATDbits.LATD1 = 0;
		}

		// every 128ms
		if(transmitCount == 16) {
			outbuffer[5] = transitionCount;
			
			//going too fast?, turn the motor off
			if(transitionCount > transitionTarget) {
				LATDbits.LATD1 = 0;
			}
			//going too slow? turn the motor on
			else if(transitionCount < transitionTarget) {
				LATDbits.LATD1 = 1;
			}
			//otherwise, flip its state
			else {
				LATDbits.LATD1 = !LATDbits.LATD1;
			}

			transmitCount = 0;
			transitionCount = 0;
		}
	
		// Check if USB is busy?
	      HIDTxReport(outbuffer, DEVICE_SENDDATA_SIZE);			// Send USB data.
		transmitCount++;
	}