Retro USB – So Close, So Far
The mess on my desk is growing. In my attempt to use USB keyboards and mice with an old ADB-equipped Macintosh, I’ve got something that’s tantalizingly close to working. On the ADB side, I can simulate an ADB keyboard and mouse, complete with ADB collision detection and address remapping. It works great on the Macintosh, when I write a routine to generate fake input data. On the USB side, I can read a USB keyboard and mouse at the same time, through a hub. I can log all the collected data over the serial port. And I can do all of this from within the same program running on my PIC32MX230. Just not all at once.
Each interface seems to work fine by itself, but things go poorly when they’re combined. The USB-related interrupts introduce timing errors into my software bit-banged ADB implementation, so that ADB no longer works. Or if I disable interrupts during ADB activity, so the bit-banged ADB timing isn’t affected, then USB breaks in strange and subtle ways. USB devices will just stop reporting status after a short while, or attach/detach events will get missed, or the program will sporadically reboot itself. This is the problem of multi-tasking two interfaces that I anticipated last year while planning this project, and now here it is.
USB Mysteries
I know very little about how USB works “under the hood”, and would prefer to keep it that way, but it seems I’ll need to do a deep dive into the USB stack code. The multi-tasking interface failure is disappointing, because I’d almost convinced myself that the USB Host implementation should be able to tolerate occasional disabling of interrupts for the 5-10ms needed to handle an ADB transaction. Yes, USB activity is supposed to happen every 1ms, so 5-10ms with disabled interrupts would mean a lot of missed USB windows. But as the USB host, I initiate all USB activity, and ultimately I should get to determine when USB activity happens, whether that’s every 1ms or not. I was a bit surprised that it didn’t work, and even more surprised that it sometimes causes the program to reset. Maybe a buffer overrun somewhere causing a crash, or an intentional error handling mechanism somewhere in the USB stack?
I used a logic analyzer to observe that there’s still USB activity on the bus every 1ms, whether interrupts are disabled or not. That much must be performed in the PIC32’s USB peripheral, without any dependency on interrupts. But when interrupts are disabled, the USB traffic seems to consist entirely of 2.59 microsecond bursts that I’m guessing are the SOF (start of frame) packet and nothing else. With the ADB side disabled, I see occasional longer bursts of USB activity up to 126 microseconds mixed in with the shorter SOF-only packets. I assume these are the HID requests and reports, which seem to come every 10ms regardless of whether I request them more often.
I was able to partially debug what’s happening when input devices stop reporting status. The program appears to get stuck in a state where it requested an HID report, and is waiting forever for the reply. I’m not sure what caused that, or why there isn’t some timeout or error-detection in the USB stack. Perhaps there is, at a lower level of the USB stack that I haven’t yet examined, but then I need to understand why it’s apparently not working. Maybe I can fix this with some timeout-and-reset code, but there’s also the deeper problem lurking of the sporadic random reboots when multitasking ADB and USB. That scares me.
Trivia
Some interesting little discoveries I’ve made while troubleshooting:
- The circuit draws 106 mA from the Macintosh’s ADB port, with my hardware plus an unpowered USB hub, one keyboard, and one mouse. That’s well within the 500 mA limit, and even within the lower 200 mA of the Powerbook computers.
- The axis data from my USB mouse is all backwards and confused. It’s something like Y axis movements reported on the X axis, and the wheel and X axis movements combined on the wheel axis. This probably means there’s a bug somewhere in the HID Report Descriptor parser in the USB stack. As I understand it, that’s the code responsible for understanding what format the report data will be delivered in.
- The USB mouse will detach and reattach itself every 30 seconds, if I don’t request an HID input report from it. As long as I request periodic input reports from it, it stays attached. In contrast, my keyboard is happy to sit there forever on the USB bus without me ever requesting an input report from it.
3 Comments so far
Leave a reply. For customer support issues, please use the Customer Support link instead of writing comments.
I found at least one cause of the random reboots. If a USB error interrupt occurs, and the code can’t determine which USB address the error was for, it will dereference a NULL pointer and crash. This led me to see that many USB errors are occurring, when the ADB code disables all interrupts for 5-10ms for ADB processing. Here are the error descriptions, from the PIC32 datasheet:
EOFEF – EOF error. This type of error occurs when the module is transmitting or receiving data and the SOF counter has reached zero
BTSEF – bit stuff error. Packet rejected due to bit stuff error.
BTEOF – bus turnaround time-out. This type of error occurs when more than 16-bit-times of Idle from the previous End-of-Packet (EOP)
I’m not sure what a bit stuff error is, but the other two are clearly some kind of USB timing-related error. That makes sense, because disabling interrupts during ADB processing is going to impact the USB timing.
I know you want to keep the cost and complexity of the project low, but adding secondary microcontroller just for handling ADB is not a such a bad idea. Just use a protocol that can be DMA-ed out and doesn’t waste CPU time. Don’t know about PICs, but on Cortex-M3 something like timer triggered memory-to-peripheral DMA could be used to do the transfer.
Nevertheless, I’m eager to see how this project works out.
Yup, I’d considered the two MCU approach, but I don’t think it will be necessary. I think it’s actually working now! I increased a USB parameter called the SOF threshold, and then I was able to read a USB keyboard and mouse while also simulating an ADB device at the same time. It seemed to work, running for several minutes with no USB errors. But then I noticed I was getting a brown-out reset whenever I lifted the mouse off the desk, or did something to illuminate a keyboard LED. And now I’m getting constant brown-out resets whenever I turn the thing on. Maybe I bumped a wire somewhere and created a short-circuit on the breadboard, but I don’t see it. I’m crossing my fingers…