BMOW title
Floppy Emu banner

USB to ADB – Multitasking Two Interfaces

vl_mouse_label

It’s time for a new progress report on my USB to ADB converter project! My goal is to design a simple PIC32 device that enables USB keyboards and mice to be used with vintage ADB-based Macintosh and Apple IIgs computers. You can read my earlier reports here: 1, 2, 3, 4, and 5. I don’t know when I’ve ever spent so much time investigating the feasibility of a project, without actually doing the project. Hopefully all this preparation will pay off.

Good news: I spent some time plowing through the details of Microchip’s USB library, as well as Suwa Koubou’s modified version, and things are beginning to make much more sense. Hosting a “simple” USB HID device like a mouse or keyboard is proving to be much more complex than I imagined, once you factor in all the behind the scenes work that would normally be handled by an OS or driver code. While I certainly don’t understand it all, I can say I understand the basic framework now. That should make life much easier as I go forward.

Bad news: the Microchip PIC32 tools and libraries continue to disappoint. On top of my previous complaints, I’ll add the poor quality of the Microchip USB stack source code. It looks like something that was hacked together by a team of student interns over a period of years. It’s a confusing and messy overall structure, with heavy use of #defines for function names, many lines commented out, comments that says useless things like “HHHHHHHHHHH”, hard-coded assumptions, debug print messages… not good. Maybe I’m looking at the wrong code? It definitely doesn’t feel like a professional-quality software library you can rely on for a real product.

More bad news: I’ve also discovered that the MPLAB X debugger is flaky. It will often stop in the middle of a debugging session and report “user program finished”, even when I can tell the program is actually still running. And I found that if you set a breakpoint while the program is running, it won’t necessarily work. You have to pause the program, set the breakpoint, and then resume. I only discovered that by accident in a moment of frustration.

The urge to throw the PIC in the trash and start over with something like the SAMD21 is growing, but I still haven’t done it. I keep rationalizing that I’ve already come this far…

 
Multitasking ADB and USB

The major bad news relates to servicing the ADB and USB interfaces simultaneously. I wrote about this concern previously: if USB-related interrupts happen too often and take too long to service, they may interfere with the software-derived timing of my bit-banged ADB interface. I did some earlier tests by setting breakpoints inside the USB interrupt handler, and determined that interrupts are only used for USB device attach and detach events. So my concern was unfounded… or so it seemed. In reality I was bitten by the breakpoint bug that I only recently discovered, and the USB interrupt actually fires at least once every millisecond, plus additional times when transfers are completed. Doh!

I thought I might still be OK if the USB interrupt handler was very short, so its impact on ADB timing would be minimal. ADB data is encoded at 1 bit per 100 microseconds, and given its encoding method, a timing error of up to 10-15 us could probably be tolerated without causing errors. Using the PIC32’s built-in core timer, I was able to measure the typical and worst case execution times of the USB interrupt handler, and it was about 50 microseconds at 80 MHz with code compiled using -O1 optimization. That’s definitely too slow to avoid causing ADB timing problems. What’s worse, for the final device I’d planned to use a cheaper version of the PIC32 that only runs at 40 MHz, so the interrupt handler time would actually be 100 microseconds.

So it seems that naively combining a USB keyboard/mouse example with my bit-banged ADB implementation is not going to work. What are my alternatives?

 
UART Hacking

If there were a hardware ADB peripheral on the PIC32, then there would be no problem. ADB bytes could be read in/out of a buffer by the hardware peripheral in parallel with USB activity or other unrelated code, and then handled later in the main loop via polling, or via an ADB interrupt. Of course there is no ADB peripheral, but maybe the UART could be abused to do the job? ADB is sort of like serial communication, in that there’s no independent clock, and it runs at a fixed bit rate. But it’s unlike serial in the way bits are encoded. Instead of a simple high voltage for logical 1 and low voltage for logical zero, ADB encodes every bit as a pulse whose width determines its logical value. There are also some non-data signalling elements in a typical ADB transmission. But maybe I could run the UART at like 10x the actual ADB data rate, and capture the oversampled ADB waveform in a buffer for later analysis through software.

I think this UART hacking idea might almost work, except that serial communication always involves a start bit, at a minimum. It’s how the UART knows that a new byte has begun, but ADB communication doesn’t include a start bit. That would likely cause weird problems when reading and writing UART-as-ADB data. Maybe this could be overcome… I’ll need to think about it more. Unfortunately my PIC32 dev board doesn’t have the UART pins broken out to a header, or to anywhere I can solder, so I’d need to get new hardware if I want to try this route.

 
SPI Hacking

If the UART peripheral can’t do the job, maybe the SPI peripheral can? SPI uses an explicit clock signal, which ADB lacks, so that’s obviously a problem. But there’s no framing or start bits to worry about, which is good for the kind of hackery I’m envisioning. If the microcontroller were configured as SPI host, then it would wiggle the clock line for SPI reads and writes, but I could leave that signal unconnected and ignored. It’s so crazy, it just might work. But again, my PIC32 dev board doesn’t have the SPI pins broken out anywhere, so I’d need new hardware to explore this.

One point I’ve glossed over with both the UART and SPI hacking ideas is that ADB is an open collector bus. The host and devices pull it low when needed, but never actively drive it high. Instead, there’s a pull-up resistor to do that job. I’d need to include some extra circuitry to adapt UART or SPI traffic to an open collector bus.

 
Timer Hacking

Another approach would be to replace my software-derived ADB timing with hardware timers and pin state change interrupts. For reading ADB traffic, instead of polling in a tight loop in order to measure the widths of pulses on the ADB bus, I would use a pin state change interrupt and then determine the pulse width by checking a timer value in the interrupt handler. For writing ADB traffic, I would use hardware timer interrupts to change the ADB bus value at appropriate intervals. That would eliminate busy loops, and enable the USB and ADB communication to happen in parallel to some extent.

This would only work if the ADB pin state change and timer interrupts had higher priority than the USB interrupt, else USB interrupts would still introduce ADB timing errors. And this approach might possibly introduce USB timing errors, if the USB interrupt handler were delayed from executing by one of the ADB-related interrupts. Another concern is the latency for context switching and entering the ISR handler, after a pin state change occurs. This would need to happen quickly, even if the USB interrupt handler had been in the middle of executing, in order for ADB timing measurements to be correct.

 
Two Microcontrollers

My final idea is to use two wholly independent microcontrollers: one for ADB and one for USB. They would communicate with each other using SPI or another method supported with a hardware peripheral. That would probably work, but wouldn’t exactly be simple, as now I would have two different microcontrollers each managing two different interfaces in parallel. It would also increase the cost and complexity of the board, and make firmware updates more difficult. But it still might be a reasonable solution overall.

To be honest, I don’t love any of these ideas. I’m sitting on my hands for now, hoping that a better approach will jump into my mind. Here’s hoping! πŸ™‚

Read 30 comments and join the conversation 

30 Comments so far

  1. Chris M. - April 7th, 2016 4:18 pm

    Can’t you tell ADB was designed by Woz? Cheap to implement in hardware, clever and elegant in design, but a total PITA to actually work with.

    Meanwhile USB is the exact opposite.

  2. Ari Kalish - April 7th, 2016 4:51 pm

    We had a similar project at my last job (1-wire and Ethernet) and ended up using a PIC32 and a PIC16 connected via I2C. We were able to push firmware down the wire fairly easily and at reasonable speeds. Updating the PIC32 was much more challenging than updating the PIC16.

  3. Andrew H - April 7th, 2016 6:08 pm

    “I keep rationalizing that I’ve already come this far…” Sunk costs fallacy, Steve πŸ˜›

    Would you be able to handle the ADB bitbanging in something like a regular old atmega328? one expensive and one cheap MCU like Ari has mentioned above might work.

  4. Ben Combee - April 7th, 2016 6:36 pm

    How about a Propeller? It’s got a 80 Mhz CPU that runs as eight separate time-sliced tasks, and someone already wrote a USB host system for it to connect with a Bluetooth dongle. You could dedicate one core for USB work, one for ADB work, and use a couple of the others for managing the data conversion.

  5. David Kuder - April 7th, 2016 8:08 pm

    It looks like the USB Host code for the Prop uses 4 of the 8 cogs and is very picky about compatibility. Each of the cogs only has direct access to 500 words of memory, access to hub ram drops the speed dramatically, since each cog has access to the hub once every 16 clocks. It is worth investigating, but I don’t know if it would be feasible for both mouse and keyboard using one device. It could quite easily handle PS/2 communication though.

  6. Steve - April 7th, 2016 8:35 pm

    I was sort of face-palming as I read through the USB code, thinking “all the PIC-haters from the blog were right!” πŸ™‚ Oh well… a different mcu would probably have a different set of challenges, and despite its appearances the code seems to work. This may be one of the few cases where the multiple cores of a Propeller would really be useful, but I don’t realistically see myself going that way. It’s just too… odd. Regarding a possible two-mcu solution, the irony is that something like an Atmega328 would be more expensive than just throwing on a second PIC32. At the very least I think a two-mcu solution should use two parts from the same vendor or family, like two PICs of different types or two Atmel parts, in order to keep the software effort more manageable.

    @Ari Kalish, why did you need two mcus for 1-wire and ethernet? Aren’t there hardware peripherals for both that support parallel operation? What sort of trouble did you run into trying to do it with a single mcu? That would be helpful to know, because whatever you ran into could potentially doom my UART/SPI/Timer hacking ideas as well.

  7. Steve - April 7th, 2016 8:38 pm

    And yes, sunk cost fallacy. πŸ™‚ If I could find a few good USB example programs for the SAMD21, I would probably jump to it. Especially since several of my UART and SPI hacking ideas would require buying new hardware anyway.

  8. nn - April 8th, 2016 12:03 am

    The STM32-series have excellent USB support as far as I know, and they seem to be very popular so there’s lots of examples and talk online.

    I have to say I sort of like the idea of using SPI without connecting the clock line.

    I trust you’ve seen this?

    https://mac68k.info/forums/thread.jspa?threadID=271

  9. Ari Kalish - April 8th, 2016 5:20 am

    @Steve:

    We had a hub and spoke architecture that allowed for various output protocols (1-wire, UART, a custom RS485-based bus) and didn’t have enough peripherals on the PIC32 to handle what we wanted to do in the long term so we never tried putting 1-wire on the PIC32. I don’t think there’s a hardware 1-wire module on the PIC32s, is there?

    We had a bit-banged implementation of 1-wire (based heavily on the sample code from Maxim) we’d used on other PIC16 and PIC18-based products that we were able to quickly port. We did run into some issues with 1-wire and the ADC interrupts on the PIC16. 1-wire transactions would get interrupted by the ADC interrupts and cause problems. Our requirements allowed us to run the ADC updates on the main loop, though, so we simply did that.

    To avoid I2C interrupts causing issues we had the slave return a busy bit in the first byte of the I2C response when a 1-wire operation was in progress. The master would enter a wait-check loop until the busy bit was cleared (or there was a timeout). That worked well for 1-wire because all events are host-driven there.

    We used UART interrupts on the slaves when dealing with serial traffic and sent an interrupt line up to the host when data was available. When it was asserted the host would read data until the flag cleared. The host read was fairly fast and the interrupt handler made sure we didn’t miss any data on the slave side.

  10. KWP - April 8th, 2016 5:20 am

    I know you had mixed experience with CPLD use on previous designs, but this would seem to be a good fit for a small programmable logic device to FIFO data to/from the ADB, using a state machine to replace the bit-banging software code, and allow it to run in parallel with the USB stuff in the PIC32?

  11. Steve - April 8th, 2016 6:46 am

    Thanks Ari, that sounds like a complicated setup! KWP, I think a second small mcu would be preferable to a CPLD here, since I don’t really need the ~1 microsecond response time of Floppy Emu’s disk interface.

    Yes, I’ve seen bbraun’s previous work on a similar device (the mac68k.info link), and talked to him about it. I know he ultimately ran into a reliability problem that I now recognize may be exactly what we’re discussing here. I believe he used the “timer hacking” approach that I outlined above, and ran into problems on the USB side where the mouse and keyboard didn’t reliability enumerate at startup. He suspected it was due to the ADB-related timer interrupts interfering with USB operation.

    I thought of a potentially fatal flaw with the UART and SPI hacking ideas. There’s a signal called SRQ that an ADB device must assert during a specific ~100 microsecond time window during receipt of a transmission from the ADB host – more details at http://www.bigmessowires.com/2016/03/30/understanding-the-adb-service-request-signal/ . I’m pretty sure that will be impossible if the ADB transmission is received as UART or SPI and analyzed in software later, as I’d been considering doing. Unless there’s a clever solution to that, I think it rules out those two ideas.

    That leaves only the timer hacking option, or using two mcus. One possible other option I didn’t mention is to modify the Microchip USB stack to make the USB interrupt handler very short and fast, so the impact on ADB timing is small enough to avoid problems. I’m not sure if that’s possible – the interrupt handler would need to be about 10x faster than it is now. It’s probably not a practical solution, but I’ll include it in the list anyway.

  12. Ari Kalish - April 8th, 2016 7:05 am

    @Steve:

    That particular product was a test fixture for other products, so we needed to be extremely flexible. We planned on connecting up to 8 independent 1-wire buses through one host. I left before that implementation was built, though.

    On the whole the system was actually fairly simple. Commands came in through the host, were forwarded to the slave, and the response was returned up the chain.

  13. Peter - April 8th, 2016 9:08 am

    Have you considered using something with strong USB host capabilities? Something in the beaglebone ballpark? Or RaspPi Zero? Then your job would be to pass along keyboard and mouse events through GPIO in adb format.Might be easier than trying to debug two microcontrollers talking to each other.

  14. Tim Buchheim - April 8th, 2016 12:18 pm

    Raspberry Pi seem like such overkill (running Linux on a keyboard converter?!?!), but it should be an easier route to get this done. USB is built-in, and at 700+ MHz the CPU should be plenty fast enough to bitbang ADB. πŸ™‚

    If the Raspberry Pi Zero ever becomes available again, it would be cheap (It was $5 when Adafruit had it in stock!) and small enough to integrate into a reasonably small case.

  15. Steve - April 8th, 2016 6:20 pm

    I’m not convinced a Pi or Beaglebone would solve the problem any better. Can you actually write a user-mode Linux program that bit-bangs the ADB protocol with ~10 microsecond-level timing accuracy? Wouldn’t it be affected by other interrupts just as much as the PIC32, if not more?

  16. Steve - April 8th, 2016 7:36 pm

    I may have to take back the mean things I said about the Microchip USB stack. In truth, I’m having a hard time finding what the Microchip USB stack even is. The code I looked at was part of a Microchip mouse/keyboard example, but I’m now realizing it may have been modified for that example and isn’t “the” USB stack. As far as I can tell, the official Microchip USB stack for new designs is now part of their Harmony framework, which the developer community has largely rejected and isn’t widely used. I don’t want to go that route, but I can’t even find a download link for an older, stand-alone USB stack from Microchip. And it was only in 2014 that Microchip added hub support, so if I do find an old version, it can’t be older than that.

  17. Jonny - April 8th, 2016 8:56 pm

    SpritesMods went for the dual processor approach here http://spritesmods.com/?art=macsearm&page=4

  18. David Kuder - April 9th, 2016 4:18 am

    Pi is out, but BeagleBone has the PRU which can be programmed to handle ADB on its own. But again overpriced and overkill for the job at hand.

  19. Different Steve (no, not THAT one) - April 10th, 2016 10:06 am

    I worked with the Microchip stack a while back putting together the TuneConsole. I had much the same impression — that it wasn’t well written. The real killer, however, was the licensing.

    At least at the time, it wasn’t redistributable, which is a bad deal for an OSHW project.

  20. Alexcat3 - April 12th, 2016 5:10 am

    I haven’t done anything with microcontrollers yet, just read about them, so this is probably a stupid idea, but:
    What if you used a combination of the bus-hacking (spi or UART, I don’t know enough to tell which would work better) approach and the timer hacking GPIO approach. I don’t know much, but it seems that your problem is that anylizing the data you receive later (like you would do with a UART or SPI bus) doesn’t work because you need to send a signal during a 100ms window, and using interrupts to anylize it as you get it is a problem because it could interfere with the USB code, which doesn’t like being delayed. So what about doing both- connecting the transmit line to both a bus and a GPIO pin, mostly transmitting and receiving data using one of the busses, so that you can do most of the processing when the USB code is free, but having one interrupt that runs at the beginning of that 100ms window, and checks if the GPIO pin needs to be set. Since the interrupt would’ve just check if a variable was true and set a GPIO pin, it might not take long enough to interfere with the USB code– I don’t know enough about the speed at which USB runs to say.

  21. Steve - April 12th, 2016 7:45 am

    Interesting thought. I don’t think it’s necessary to go to that length. The timer hacking approach that I described above will work, I think. It’ll just be more complicated to implement. Basically it means replacing my ADB implementation, removing all the blocking code and software delay loops, and replacing it with interrupts driven from timers and pin state change events. I discovered that the PIC32MX has an input capture feature with a 4-deep FIFO that can help with this. I’m still a little bit worried that interrupting the USB interrupt handler will cause USB errors, but I think it won’t. So I’m going to move forward with this approach, and see how far I get.

  22. Jean - April 12th, 2016 9:13 pm

    Take a look at this project : https://github.com/tmk/tmk_keyboard

    It works on a single 8 bit AVR !

  23. Steve - April 12th, 2016 9:45 pm

    I looked at that a while back, but convinced myself it wasn’t relevant since it goes the opposite direction of the way I’m going, and doesn’t need to worry about all the USB host and hubs stuff. But the timing issues of multiplexing ADB and USB should be similar. I just peeked at the source code, and it disables interrupts throughout the entire bit-banged ADB transaction! That’s potentially as long as several milliseconds, which would cause several entire USB frames to be lost. Does that actually work??? If so, that would be waaaay easier than any of the other solutions I’ve been pondering.

  24. rasz_pl - April 14th, 2016 4:03 am

    I have a feeling you are missing the whole forest.

    1 USB does not support bus mastering. Host is king of the hill baby, and since you are the host You decide when and how often to pull devices. 125Hz is not set in stone.
    2 USB HID is only 1.5Mbit/s, no need for hardware usb host and hub chips. You can easily bitbang two host ports taking turns between keyb/mouse.

    Establish frequency of ADB polling (guessing around 60hz?), as long as pauses between packets from the mac are long enough for two usb transactions (~0.1ms to read keyboard status) you are golden.
    Listen for ADB pull, reply with empty/default answer from a buffer, immediately after pull keyboard then mouse and save answers to a buffer, wait for mac pooling, repeat.

    This + two transistors? to handle open collector is all you need:
    mouser.com/ProductDetail/STMicroelectronics/STM32F030F4P6
    but why stop there when you can buy this
    mouser.com/ProductDetail/STMicroelectronics/STM32F103C8T6

    in a form of dev board cheaper than bare chips:
    http://www.ebay.com/itm/STM32F103C8T6-ARM-STM32-Minimum-System-Development-Board-Module-For-Arduino-/201529768817

    \’dev board\’ on ebay $3 free shipping, probably cheaper than what you would pay to manufacture whole thing, so maybe even worth considering standardising on it + small addon with adb voltage conversion.

    low level usb pooling explained on usb sniffer vendor website: http://www.usbmadesimple.co.uk/ums_5.htm

  25. Steve - April 14th, 2016 6:32 am

    Good ideas. Your application can decide when to poll the USB devices, but as far as I understand there’s still always a SOF (start of frame) packet sent every 1ms. On microcontrollers with built-in USB, I think this is usually done in hardware and it generates an interrupt. That’s what happens on PIC32 at least, so you can’t avoid interrupts by changing the polling speed. Do software-USB solutions like V-USB support USB Host? I’ve never looked. I assumed it was easiest to use a microcontroller with built-in USB hardware, since there are plenty of choices and they’re not really any more expensive. Although the idea of bit-banging *two* USB interfaces is interesting, to avoid worrying about a hub.

    For this device, ADB requests are received from the Mac or IIgs about every 10 ms, but the timing varies, so it needs to use an interrupt or very fast polling to detect the beginning of a new ADB packet. Unfortunately there’s no checksum or real error-detection at all in ADB – there’s no way to know if the data you received/sent was corrupted.

    Those ST boards are tempting. My only concern is that I’ve heard repeated reports that their USB Host implementation is flakey – sometimes fails to enumerate the USB devices. Rob Braun was working on a similar USB-to-ADB device a few years ago, with an ST-board, and ran into USB-enumeration reliability problems too.

  26. Steve - April 14th, 2016 6:43 am

    How do eBay sellers have that ST board so cheap? Even in quantity 1000, its main microcontroller chip alone is $3.19. Then you’ve got the PCB, USB plug, crystal, and other components, and then the cost of shipping to the USA. But somehow the whole thing only costs $3.08.

  27. rasz_pl - April 14th, 2016 2:13 pm

    ARM is a commodity, silicon is CHEAP, and I really mean cheap cheap. my post on eevblog http://www.eevblog.com/forum/microcontrollers/cheap-arm-development-boards-with-64k-ram-or-more/msg901034/#msg901034

    \”$2-15 for a piece of silicon with a size that is a FRACTION of 2GBits memory chip, when you can buy DIMM module with 8 of those 2GBit chips at $9 RETAIL should tell you something.
    http://www.amazon.com/Crucial-PC3-12800-Unbuffered-240-Pin-CT25664BA160B/dp/B006YG88QC
    just let that sink in, ~16 billion transistors / $9 = ~1.7 billion transistors for a dollar, retail
    Cortex-M3 has afair ~100K transistors, another 700K for fattest sram/rom option and we end up at ~1mil transistors. 1000 less than what you get in a ram chip at similar price point.

    Whole ram/flash price differentiation is a huge scam, the most expensive part of any ARM microcontroller is packaging
    Most lower end stm32 parts are same silicon across whole family, just fused differently. Sometimes they dont even bother and you end up with stm32f103c8t6 (theoretical 64KB flash) having 128KB.\”

    Fun fact: Rockchip was selling >1GHz quad core ARM v7 +pmic chip combo at $4 two years ago, this forced Intel to sell Atoms below its manufacturing costs to get any design wins in China at all. After Qualcomm was slapped with $1B in fines (for IP license price gouging) Intel changed its tune (probably scared of impending price dumping prosecution) and went into \”cooperation\” (read \”invested\” aka paid $1.5B and gave away x86 license) with huge Chinese semiconductor manufacturers (rda, spreadtrum, and archenemy rockchip).

    ST/TI/NXP/Microchip and other big manufacturers have no problem price gouging western customers, but in China they face real competition basing its prices on actual fab cost. $5 pee zero is not some crazy subsidized price, it is a real cost of parts + very slim margin, and that thing packs ~4 billion transistors. Microcontrollers listed at $2 on mouser are being sold to chinese partners for a fraction of that price, its either that or being replaced with STC/GD/Nuvoton/etc.

    Lazines, vendor lockin, legacy, apathy and sunk cost fallacy are main Microchip business drivers.

  28. rasz_pl - April 14th, 2016 2:16 pm

    Back to technical:
    in low speed its keep alives, and they dont need to be precisely every 1ms, just whenever its convenient for you the host, they arent used for timing/realtime events because those dont exist in low speed mode (no isochronous/bulk for HID class).

    V-USB doesnt support USB Host out of the box, its something you would have to write, but its not like implementing whole USB, HID class is a very small and narrow subset and shouldnt be difficult. Here is a project you can use to learn how to bitbang usb host (GNU license so cant copy verbatim unless you also publish your source), already supports usb mouse:

    https://courses.cit.cornell.edu/ee476/FinalProjects/s2007/blh36_cdl28_dct23/blh36_cdl28_dct23/index.html

  29. Vincent - April 15th, 2016 6:54 am

    Rather all this USB stuff, how about a Bluetooth Keyboard/Mouse module to ADB?

  30. Peter - June 10th, 2016 10:17 am

    I’ve always thought an adapter going the opposite direction would be even better. Given that I an easily run an emulator for a vintage Mac on my modern Mac, to complete the experience I’d like to use an older keyboard, so converting from ADB to USB such that I can plug the ADB keyboard into my modern Mac would be really really cool. And while you are at it, how about supporting the Lisa and RJ11 style Mac keyboards too. πŸ™‚

Leave a reply. For customer support issues, please use the Customer Support link instead of writing comments.