I did a preliminary input capture version for receiving from the keyboard and it does time it more accurately in the face of interrupts, but didn't perform any better than the basic rewrite I've done. And its send timing is still affected by interrupts, so it seems the only reasonable alternate version is a full interrupt-based one. But, I've been researching the LUFA USB driver and it seems that
delaying interrupts isn't a problem with the hardware-based USB. Also, it apparently doesn't really support re-enabling interrupts in its interrupt handler, so an interrupt-based ADB would still get delayed. It sounds like disabling interrupts for the duration of ADB transfers would be fine for the hardware-based USB implementations. And assuming that LUFA is similar to the PJRC basic USB keyboard driver, the interrupt overhead doesn't mess up ADB timing enough to matter.
Here's the updated adb.c and the simple ADB-USB converter I wrote (with a makefile):
blargg-adb-usb-1.zipThe above can be used as a standalone ADB-USB converter (I'm using it right now to type this).
The new ADB code doesn't disable interrupts at all. You could do that around the calls to the adb code. I think if you're going to disable them, you could do it for the entire call, not just when the keyboard is talking.
Changes
-----
* Documented header and source a little more and added some symbolic names for error values.
* Rearranged functions in source to not need prototypes.
* Merged lots of functions where there was no loss in clarity.
* Added adb_host_kbd_modifiers() to get keyboard modifiers, just because it was only 3 lines of code and it could be useful to someone in the future for a keyboard driver.
* Added config.h support for setting ADB_PORT etc.
* Eliminated constant reconfiguring of ports every time they're used. We can set them up once in init and depend on them not being messed with. If someone else is screwing with our configuration, then that needs to be fixed, rather than us constantly reconfiguring them.
* Changed to using open-collector output and relying on external 1K pull-up resistor. This is how ADB devices do it and avoids possibility of us driving bus high while keyboard pulls it low.
* Moved bit-reading code into adb_host_talk so that we can report any bit timing errors rather than ignore them.
* Optimized code to be smaller and thus have less timing overhead.
Timing fix
----------
The biggest issue was wait_data_lo() and wait_data_hi(). Their loop delayed delayed 1 us in *addition* to the loop overhead, which was 1.5 us on its own due to gcc not inlining and the data_in() reconfiguring the port every time. This could have been throwing timing off so that it was just on the edge of working, failing when timing was just slightly off.
Now the code compensates so that loop is exactly 1 us (16 cycles). This allows return value to be used to calculate how long pulse was in the bit reading code. In addition, it logs every timing value so that we can look at them later and see how well within margins it's timing things, and be sure that the times it's measuring match the times of the actual pulses coming in. It just puts them into an array for examination by the calling code.
The new bit reading code times the low and high portions, then determines whether it's a 0 or 1 based on which was longer than the other, rather than looking at absolute times. In the simulator it's show itsely very robust to wide variations in timing.
The timing is still affected slightly by the overhead between calls to wait_*, but it's only a few us.