Sunday, December 29, 2013

Been working on MACH 3

Matteo Marioni, Warren, and myself have been jointly trying to test Dexter with MACH 3.  This is difficult at the moment because Teo is the one with the game but he does not have a VBI Injector board so we've tried to rig up a workaround for the time being.  After analysis of the MACH 3 hardware, we've concluded that the Manchester picture number can be encoded in a visible line of the video which means that the Raspberry Pi can render it without external help.  So we created a laserdisc image file and tested it out.

Early results were encouraging but not was encouraging as we'd hoped.  By adjusting the voltage threshold of the picture number comparing hardware, we were able to get the game to find the picture number some of the time (maybe 50% if my understanding is right) but still find garbage the other half of the time.  This is quite puzzling.  I really wish Teo had an injector board!

Another problem is the target data encoded in one of the audio channels on the disc.  Early tests showed that trying to parse the audio data from the Raspberry Pi were resulting in complete garbage.  I can think of a few things that could be wrong: a) the audio data may be offset from where it is supposed to be on the original laserdisc (a common problem with audio capturing hardware), b) the audio voltage level output by the Raspberry Pi may be too far off from the voltage level output by the original PR-8210.

Unfortunately I have neither a MACH 3 laserdisc nor a PR-8210 player so I am unable to test the voltage level coming from the audio jack on the back of that player when the disc is playing to see what the proper range should be.

At any rate, I am going to venture a guess that getting MACH 3 to work properly with Dexter may be one of the three most challenging tasks to overcome, the other two being Star Rider and Freedom Fighter (the latter possibly being the easiest of the three).

Thursday, December 26, 2013


PR-8210 support for Dexter is "finished" but remains untested on Cliff Hanger and MACH 3 hardware (the two main targets).

Warren recently tested it on his Cobra Command conversion for Cliff Hanger boardset and discovered that the picture number from the video signal is apparently completely ignored.  The way that the game decides when a seek has finished is to watch for the PR-8210 to drop the video signal and then restore it.  In other words, it appears that all PR-8210's drop the video signal during seeks (and maybe all laserdisc players in general do this and we've just never noticed!).  If this is true, then it introduces a mystery as to what exactly needed to be different about the PR-8210 player for MACH 3.

Anyway, the gist of it is that Cobra Command (Cliff Hanger conversion) is now tested and working with Dexter.

Our next target is to test with MACH 3 because we know for sure that it definitely reads the picture number from the video stream.  We know this because MAME and Daphne both emulate MACH 3 and provide this behavior.  Cliff Hanger also reads the picture number from the video stream (verified by the same reason).

Wednesday, December 18, 2013

When will Dexter be ready?

Although there is no formal release date for the next run of Dexter boards, here is what I can tell you:

I am extremely motivated to finish this project and am working on it constantly.  I am trying to prioritize finishing laserdisc players that have the most games associated with them.  This is why I did LD-V1000 first, then Sony LDP-1450, and now PR-8210 (Neil W. contributed the firefox player code).  I also tend to prioritize doing what will make the finished product as easy for the customer to use as possible (I've learned by working on Daphne that releasing something very hard to use is just not worth it).  I did release the rev2 board which falls in the "hard to use" category but that was because I needed testers and also capital to make the boards in the first place so I could keep development going.

It's been about 2 years since I made the rev2 boards (December of 2011).  I think people have been very patient and therefore I really want to release a rev3 to "the world" soon, hopefully within the next 6 months (but no promises).

My plan for rev3 is that it will be somewhere between a completely finished product and a product still in development meaning that it will probably be something like this:
- two bare boards (Dexter and Raspberry Pi) that will sit inside your cab (we'd like to come up with a fancy way to protect the boards and mount them but I don't want to delay production for people who want them Right Now and have waited so long)
- Officially supported modes will be LD-V1000, VP931 (firefox only), LDP-1450, and PR-8210.  The rest of the modes will be included on the LEDs on the board (like rev2) but will be clearly marked as "not working, may never work" on the silk screen so that there are no false expectations.
- During this time, I will keep working on releasing new modes such as PR-7820, Hitachi (astron belt), VP932 (PAL euro), PR-8210A (Star rider), VP-931 (for freedom fighter), Simutrek (cube quest).  I'll do my best to predict whether I can implement these modes before actually doing it.
- LDP-1000A and VP-380 will _not_ make it in.  These require a new architecture.
- Updating the firmware will be easy (much easier than rev2).  You won't need to program the microcontrollers as I've written a bootloader so that they can be automatically updated via the raspberry pi's serial port.
- We will also need to bottom out on how to provide the disc images to the customer.  I may write a "Dexter Loader" spin-off of DaphneLoader that will load the images and software/firmware updates to a USB thumb drive which can then be inserted into the pi.
- Price of the boards will be determined by how many people pre-order.  My minimum goal will be making 50.  I will be hand soldering prototypes ahead of time to make sure the bulk production run is correct.

As you can see, there is still a lot of work to do, but we are closing in.

OpenBench Logic Sniffer upgrade HOWTO

I got one of these about 6 months ago but only recently started messing with it.  I wanted to make sure my firmware was up to date so I started reading up on how to update it and quickly hit a brick wall of confusing, outdated and obsolete information.

Updating the FPGA bitstream (to v3.07) seemed okay but trying to update the PIC code was not working at all for me.

Here is the best guide I've found:

I did this on linux.  The problem is when you go into update mode, the /dev/insert-your-usb-device-here goes away after a few seconds so passing it in on the command line is problematic.  I discovered that if I got the command line ready, and then went into update mode (by holding down UPDATE button and pressing RESET) that the /dev/insert-your-device-here would stay resident for a brief period and I could quickly run the command line.  It worked!

See for how I got /dev/OpenLogicSniffer

The exact command line I used:
sudo /home/matt/ols-fwloader/bin/ols_fwloader -f BOOT -n -P /dev/OpenLogicSniffer -V -W -w OLSv1.firmware.v3.0.hex

Sunday, December 15, 2013

PR-8210 Dexter support almost done

PR-8210 Dexter support is almost done!  This is a fantastic milestone because it will (theoretically) unlock the following games: Cliff Hanger, Goal to Go, Bruce Jenner (hehehehe), MACH 3, Us Vs Them, Cobra Command (Cliff Hanger conversion), and Cobra Command (MACH 3 conversion).  Now that's a lot of games! :)

What's done:
- PR-8210 interpreter written and unit-tested
- test program written on another AVR to simulate sending PR-8210 serial data over the wire (so that I don't need an arcade boardset to test)
- dexter code written to interpret incoming pulses on the wire and correctly convert them into PR-8210 commands
- dexter code to inject VBI data into video stream (woohoo!)

What remains to be done:
- hooking up PR-8210 interpreter to Dexter
- implementing step forward/backward generic LDP command to Dexter (seems that MACH 3 and Cobra Command both use these as a substitute for pausing the disc)

Friday, December 13, 2013

Made progress on PR-8210 code

Last night, I've wrote a lot of the Dexter code for PR-8210 support.  It should be able to now correctly interpret incoming pulses on the wire and convert those into messages.  I still need to take the interpreter inside Daphne and modify it so it can be used with Dexter but that shouldn't be too hard.  Unfortunately, I don't have any arcade hardware which used the PR-8210 so I have nothing to test it with.  I plan on rigging up my STK600 to add as a controller.  I will need to find a jack that plugs into the PR-8210 port on the Dexter board and modify it so it can be plugged into the PR-8210.  I've gotta have a cable lying around here somewhere that will fit.  Interpreting the PR-8210 is actually pretty trivial compared to something like the VP-931 but not having an authentic way to test it is a pain.  Fortunately, Warren can test it on his Cobra Command Cliff Hanger conversion board set and at least verify that it works for one PR-8210 game.

Tuesday, December 10, 2013

VBI injection now working dynamically

After some more coding and debugging, I've got VBI injection working pretty reliably (and I am _very_ pleased about this!)

Here is what the VBI data looks like on the original Cliff Hanger laserdisc.  (I captured this from my disc)

Here is what the VBI data looks like on Dexter (using VBI injection).  Note that Dexter only is displaying the data that is described in the official laserdisc standard because this is what is needed to inject the frame number into the video stream.  Dexter does not try to re-generate the other non-standard schlop stored on the original laserdisc.

Monday, December 2, 2013

Bootloader tested and working!

I've finished writing my custom AVR bootloader.  I had to resort to some trickery in order to keep it fitting within 2k but I succeeded.

Here is what it does:

When Dexter board is powered on, bootloader checks a location in the EEPROM to see if last update succeeded.  If it did, it immediately boots the Dexter code.  So the bootloader is essentially invisible in this scenario.

If the last update did not succeed (or the bootloader has been installed on a fresh microcontroller), the bootloader sends a message on the serial port requesting for the listener on the other end to upload the firmware to it.  It will continue to do this indefinitely until it gets the complete firmware program uploaded to it.  At this point, it marks in the EEPROM that the firmware programming succeeded and boots the firmware.

This _should_ cover most disaster situations, including the power going out in the middle of a firmware update.

The only situation it doesn't cover is if the firmware itself is defective.  The firmware will need to correctly be able to initiate a self-update of itself and if this part of it is buggy, the bootloader has no way of knowing this and will happily continue to boot the firmware.  So I also made it so if you hold down the DIAGNOSE button while powering on, the bootloader will initiate the reprogramming sequence no matter what.  This is my disaster-recovery option.  And if someone does it by accident, it should just reprogram with no harm done.

I think I've covered all the bases.

Sunday, December 1, 2013

Bootloader written, but not tested

Since my last update, I've realized that I really need to write a "bootloader" for the AVR so I've been working on that over the last week.

What is a bootloader?  It's a small program that is installed at the top of the AVR's program memory and boots up first.

Why do we need one?  It's so that I can push out firmware updates through the AVR's serial port instead of requiring an ISP/JTAG programmer (such as the AVR Dragon).  This is a must-have feature for the finished Dexter product and I felt like the time was right for me to stop procrastinating on it and just write it now.

Instead of using a pre-existing bootloader, such as the Arduino's, I just wrote mine from scratch with the goal of making it as small as possible.  Total program size right now is just under 2k which is not horrible.  On Dexter's main microcontroller, this will mean that 2k of the total 64k will be used by the bootloader.

The bootloader make it so that the Raspberry Pi can reprogram Dexter.  This means I can release updates from time to time and no one will need to do anything technical to get them onto the Dexter board.

Monday, November 25, 2013

VBI injection: it works!!

I've got my VBI injection code working well enough to be able generate complete laserdisc data.  In the above example, I successfully have generated the following data:

Track 0 Field 0 WhiteFlag:1 L16:f86767 L17:f81313 L18:f83210
Track 0 Field 1 WhiteFlag:0 L16:f86760 L17:800001 L18:800002

To break that down, on line 11 I have a solid white line (white flag), and on line 16, 17, and 18, I have data that begins with 0xF8.  And so on.  This is what I was attempting to generate so to have it succeed is very gratifying.

You may notice that there is a little wobble with the white lines in that they are not consistently drawn in the same place.  This _may_ be due to the way the AVR handles external interrupts by default.  I haven't looked into this too closely yet.  I suspect it may not matter at all since the official laserdisc spec does allow for some small variation about when the line is drawn.

Friday, November 22, 2013

Wow! VBI injection is working! Holy cow!

I am super excited about this!  This is my first attempt at injecting a 24-bit picture number into the VBI of a video signal.  Yes, it's on line 11 instead of 17/18 and yes, it's not as bright as it should be, but dangit it is working!!  I had to hand-craft AVR assembly language and count each cycle meticulously to make sure I did not drift and I am pleased to say that each bit cell is "exactly" 2 microseconds long (as accurate as the clock is) which is what the official laserdisc spec says it should be.

This was really the big hurdle that I've been procrastinating for so long and now that it's finished I can do the easier stuff.

For those curious, the voltage to create this data is currently about 0.95V where the NTSC standard says that 1 V should be about max brightness.  But often this standard is somewhat ignored.  I designed the board so that I can crank up the voltage as high as about 1.5V.  The RGB values from the current voltage are 182 and they should be closer to 255 (fully white).  According to my math, I am going to need the voltage to be about 1.3V to get it fully white.  I am sure glad I made the voltage adjustable on this board! :)

More progress on VBI injector v2

So, I couldn't figure out why I was getting the magic smoke on my last board.  Turns out that I had an extra board just in case and I had even ordered a duplicate set of parts (just in case).  So I decided to solder this extra board instead and just use the Arduino.  This was a lot easier and made me wonder why I didn't attempt this in the first place.  I guess I just wanted to try my hand at surface mount soldering!

After plugging it into the dexter board, I was able to verify that the hardware was working perfectly!  My surface mount soldering skills are climbing :)  Now, I "just" need to write the software.

Wednesday, November 20, 2013

Replaced 12-pin receptacle on Dexter, but had MAGIC SMOKE upon power up

So tonight I decided to replace my 12-pin receptable which had a few pins broken off (piece of junk!!).  I also decided to straighten the wires while I was at it.

Ah, now the wires are straighter.  Feels better.

Plugs in nicely to the VBI injector board :)

It also seems to work!  But, I  happened to notice some magic smoke (and a electrical burning smell) emanating from the board.  I happened to see that it was originating from this area.  Those pads include the GND and VCC pins of the AVR.  I checked (and double checked) that there were no shorts there but I did recently glob on an overload of flux and didn't do a very good job of cleaning it off.  I thought this stuff wasn't supposed to be conductive!  I guess I'll try thoroughly cleaning it with rubbing alcohol (if I can find some) and try again.  It would be really nice if I don't fry my AVR as that will set me back a week rebuilding this board with my spare.

Tuesday, November 19, 2013

Dexter work started again!

As I said a while ago, I was temporarily suspending Dexter work in order to finish the Cobra Command for Collins project.  Now that that project is finished, it's time to resume Dexter work!

Back during California Extreme 2013, I was hastily trying to get my VBI injector board working but was unsuccessful.  I recently spent some more time troubleshooting it and found that I had failed to connect a crucial pin to the AVR microcontroller *Face palm*.  So I've just completed a very hacky soldering job to attempt to rectify this problem.  I won't be able to test it until a few days from now because I am waiting on a part to ship from Digikey.

Monday, November 18, 2013

Cobra Command For Collins : The Interview

Getting some of Leslie's thoughts.

Cobra Command For Collins : The exciting conclusion

A few talented and determined individuals labored for 6 months to create a replica arcade game for Leslie M. Collins, who is disadvantaged in life.  After knowing Leslie for 10+ years, and knowing that his dream was to own a Cobra Command arcade game, and concluding that he had done everything possible within his own means to make this dream come true, we stepped in and made up the difference.
We raised money, built (from scratch) the cabinet, painted it, got replica art work, wired in the electronics, and wrote the emulation software to play two classic laserdisc games: Cobra Command and Cliff Hanger.

Upon placing the video game at the Pinball Hall of Fame in Las Vegas, we lured Leslie to the location on pretense of just hanging out with one friend to enjoy an afternoon of pinball games.  We allowed Leslie to randomly discover this game on his own.

This video shows this moment (captured from 3 angles) and his reaction.

Upon discovering the game, he was very excited.  We let him soak it in for a few seconds before revealing ourselves from our hiding place.

Technical notes: This game runs on Daphne ported to the Raspberry Pi by myself (heavily optimized).  Cliff Hanger runs a 100% speed, Cobra Commands runs at near 100% (you can't really notice any problems).

Here are some other random video clips for those who want more of the experience.

Saturday, November 2, 2013

Should emulators be commercial (part 2)

I just read a fascinating article (see ) about why software piracy tends to unwittingly preserve digital history much more than copyright holders do.

The part that jumps out at me is that DRM (aka copy protection) seems to actively interfere with preserving software because it is designed to prevent the software from stolen (which has the side effect of the software becoming un-preservable down the road).

In my previous blog post, I mused about making parts of Daphne closed-source and partially commercial in the future because I feel like I need to be able to justify the time I spend on less interesting improvements to Daphne (ie a polished Raspberry Pi port).  It almost feels like I am somewhat forced to add some kind of DRM scheme as part of this experiment.  This puts me in a big quandary.

The crappy part about DRM is what the above article states to eloquently:
"That is why DRM feels fundamentally wrong from a humanistic standpoint: it conspires, in conjunction with time, to deprive humanity of its rightfully earned cultural artifacts."

The irony is not lost on me.  What business does an emulator have being closed-source (with DRM) when the whole mission of the emulator is to presumably preserve memories from becoming lost forever?  Could the hypocrisy be any more blatant?

I don't have all the answers right now.  But I've thought of one possible solution to this problem.

The solution is to do what John Carmack (of id Software) tends to do with his games.  He sells them commercially, lets them make their money, then he open sources them after they've lost commercial viability.  In this way, he is both getting paid for his work AND he is preserving his work for posterity.  In other words, the idea is to have a temporary DRM system that expires after a relatively short period of time.  I like this idea and I think it could work for what I am trying to do.

In case you are wondering what I think I am trying to do (and this seems to change regularly as I try to figure it out myself):

  1. I want my work to be preserved forever which means I am highly motivated to have all source code completely open.
  2. I want to be able to justify the time I spend on my less-interesting work which means I probably need to start getting paid for it, which probably means some temporary DRM system.  Hopefully the knowledge that it would only be temporary will help people tolerate it a lot better.

This seems like a good idea now, I may think it's nuts when I come back tomorrow and re-read this.

And the good news is that I won't be acting on this any time soon (maybe years) because I am still working on DEXTER.  So this plan still can do through many revisions.  If anyone has any good ideas, I'm pretty open at this point.

Wednesday, October 30, 2013

Should emulators be commercial?

There is some recent controversy in the MAME world about changing the MAME license to be commercial friendly (that's a gross simplification).

I've felt for a few years now that the only reasonable path forward for me with DAPHNE is to make it "more" commercial (not completely commercial).  Here is why:

People regularly (to this day) come to me and ask:
- Will you make an Android port of Daphne?
- Will you make a Raspberry Pi port of Daphne?
- Will you modify Daphne somehow to meet [insert need here] ?

Also, last time I went to CAX, there was at least one Daphne cabinet on the show room floor.  I expect to see more Daphne cabinets on the show floor at future CAX events as more original hardware continues to fail.  Who knows how many MAME cabinets were at CAX this year masquerading as original hardware?

The point is that demand for emulation of legacy hardware is only going to increase in the future, not decrease, because original hardware will continue to fail.

How is this demand going to be satisfied?

The current tradition is that a handful of really smart experienced people donate their expertise to writing the emulator, thus meeting only their specific needs.  The emulator is released open source with the expectation that no one is going to profit from the developer's work.  The emulator probably won't be very user friendly or meet everyone's needs because it was only designed to meet the developer's needs.

When users start to complain that the emulator doesn't meet their needs, the emulation author (rightly) is offended because he/she used his unique talents and skills to deliver something that is incredible and he/she gave it away for free, so how dare the user community be ungrateful at this gift?  The user community then shuts up and lives with a sub-optimal experience (and yes, I'd put Daphne in this category).

Is this how we're going to keep going into the future?

To me, the solution to this non-ideal solution is for emulators to become more commercial.

In my case, I don't care about an Android port.  But it _would_ be an interesting project _if_ a lot of people (who were willing to pay) were very interested in it.  Because then I would be working for appreciation (proven in the form of money) which is a good motivator for me.  As it stands, with Daphne v1.0 I wasn't working for appreciation at all, I was working on something that I personally wanted.  In other words, if I am going to be working on something I don't necessarily want, I need more than just the occasional "thank you".

Re: the Raspberry Pi port, I actually would love to release a Raspberry Pi port.  But the way people approach me about it rubs me the wrong way.  They kind of expect that I will do a bunch of work for them to deliver this port and then give it away for free.  Is this a sustainable arrangement between developer and consumer? Not for me.  That's why you don't be seeing a public Raspberry Pi port any time soon (news flash, I have a private port already finished but if I can't find a better way to release it to the public than the status quo, I won't be releasing it at all).

Re: modifying Daphne to meet some need that some user or group has.  See my point about the Android port.


The ideal scenario for Daphne for me going forward:
- I continue to keep anything related to emulation open source and free (ie BSD or LGPL styled) so that these games remain preserved, so that others can benefit freely from my research in this area, and so that the emulation developers can collaborate and optimize our shared code into one massive standard of high-performing, well-engineered code.  There is no reason for us to all be re-inventing the wheel.
- Anything related to an improved user experience that isn't emulation related and has nothing to do with preserving arcade games becomes commercial (and possibly closed source).  This would include DaphneLoader which automatically downloads laserdisc video and arcade ROMs thus vastly improving the user experience (this is already closed source).

Phrased differently would be:
- All preservation-related code (CPU's, sound cores, etc) would be LGPL'd, living in a DLL.  The idea is that having all emulation authors collaborating in one code base to make the emulation code ideal is worth the cost of people profiting off of it.
- The rest of the emulator could be any variant of commercial or open source to match the goals of the author.

This would enable users to have higher quality emulators that are specific targeted at meeting _their_ needs instead of meeting the emulation authors' needs.


I know some emulation developers are going to be offended when they read this.  And all I can say is we're going to have to agree to disagree.

I am hoping that there will be some developers out there that see my vision and realize that we need to find a way to keep writing free emulation code and sharing it between us while still being able to sustain a long-term pace that produces results desirable by the community.

Tuesday, October 8, 2013

Monday, August 5, 2013

Looks like Time Traveler is wookin'

Looks like Time Traveler's time reversal cube feature is working so I am marking that game as "Works" on my Dexter status page and upgrading the LDP-1450 implementation status back to 100% under the assumption that every command every laserdisc game will need is implemented and working.

I am going to take a little break working on Dexter to work on Cobra Command for Daphne on the Raspberry Pi (for the Cobra For Collins project) and after that I'll probably get back to work on PR-8210 support for VBI injection.

Thursday, August 1, 2013

Multi-speed and reverse playback is now complete

I've finished adding multispeed and reverse playback functionality to Dexter.  I will test it on Time Traveler soon.  If there are errors, they should be small, as my unit tests are all passing.

Wednesday, July 31, 2013

Reverse playback at 1X speed on LDP-1450

I figure since I am adding support for 3X reverse playback speed, I may as well add support for 1X reverse playback speed.  Here is how the picture numbers look when captured:

Track 0 Field 0 WhiteFlag:0 L16:000000 L17:ab0590 L18:ab0590
Track 0 Field 1 WhiteFlag:0 L16:000000 L17:fb0591 L18:fb0591 (30591)
Track 1 Field 0 WhiteFlag:0 L16:000000 L17:ab0591 L18:ab0591
Track 1 Field 1 WhiteFlag:0 L16:000000 L17:fb0592 L18:fb0592 (30592)
Track 2 Field 0 WhiteFlag:0 L16:000000 L17:ab0592 L18:ab0592
Track 2 Field 1 WhiteFlag:0 L16:000000 L17:fb0593 L18:fb0593 (30593)
Track 3 Field 0 WhiteFlag:0 L16:000000 L17:ab0593 L18:ab0593
Track 3 Field 1 WhiteFlag:0 L16:000000 L17:fb0594 L18:fb0594 (30594)
Track 4 Field 0 WhiteFlag:0 L16:000000 L17:ab0594 L18:ab0594
Track 4 Field 1 WhiteFlag:0 L16:000000 L17:fb0595 L18:fb0595 (30595)
Track 5 Field 0 WhiteFlag:0 L16:000000 L17:ab0595 L18:ab0595
Track 5 Field 1 WhiteFlag:0 L16:000000 L17:fb0596 L18:fb0596 (30596)
Track 6 Field 0 WhiteFlag:0 L16:000000 L17:ab0596 L18:ab0596
Track 6 Field 1 WhiteFlag:0 L16:000000 L17:fb0597 L18:fb0597 (30597)
Track 7 Field 0 WhiteFlag:0 L16:000000 L17:ab0597 L18:ab0597
Track 7 Field 1 WhiteFlag:0 L16:000000 L17:fb0598 L18:fb0598 (30598)
Track 8 Field 0 WhiteFlag:0 L16:000000 L17:ab0598 L18:ab0598
Track 8 Field 1 WhiteFlag:0 L16:000000 L17:fb0599 L18:fb0599 (30599)
Track 9 Field 0 WhiteFlag:0 L16:000000 L17:ab0599 L18:ab0599
Track 9 Field 1 WhiteFlag:0 L16:000000 L17:fb0599 L18:fb0599 (30599)
Track 10 Field 0 WhiteFlag:0 L16:000000 L17:ab0599 L18:ab0599
Track 10 Field 1 WhiteFlag:0 L16:000000 L17:fb0599 L18:fb0599 (30599)
Track 11 Field 0 WhiteFlag:0 L16:000000 L17:ab0598 L18:ab0598
Track 11 Field 1 WhiteFlag:0 L16:000000 L17:fb0598 L18:fb0598 (30598)
Track 12 Field 0 WhiteFlag:0 L16:000000 L17:ab0598 L18:ab0598
Track 12 Field 1 WhiteFlag:0 L16:000000 L17:fb0598 L18:fb0598 (30598)
Track 13 Field 0 WhiteFlag:0 L16:000000 L17:ab0597 L18:ab0597
Track 13 Field 1 WhiteFlag:0 L16:000000 L17:fb0597 L18:fb0597 (30597)
Track 14 Field 0 WhiteFlag:0 L16:000000 L17:ab0596 L18:ab0596
Track 14 Field 1 WhiteFlag:0 L16:000000 L17:fb0596 L18:fb0596 (30596)
Track 15 Field 0 WhiteFlag:0 L16:000000 L17:ab0595 L18:ab0595
Track 15 Field 1 WhiteFlag:0 L16:000000 L17:fb0595 L18:fb0595 (30595)
Track 16 Field 0 WhiteFlag:0 L16:000000 L17:ab0594 L18:ab0594
Track 16 Field 1 WhiteFlag:0 L16:000000 L17:fb0594 L18:fb0594 (30594)
Track 17 Field 0 WhiteFlag:0 L16:000000 L17:ab0593 L18:ab0593
Track 17 Field 1 WhiteFlag:0 L16:000000 L17:fb0593 L18:fb0593 (30593)
Track 18 Field 0 WhiteFlag:0 L16:000000 L17:ab0592 L18:ab0592
Track 18 Field 1 WhiteFlag:0 L16:000000 L17:fb0592 L18:fb0592 (30592)
Track 19 Field 0 WhiteFlag:0 L16:000000 L17:ab0591 L18:ab0591
Track 19 Field 1 WhiteFlag:0 L16:000000 L17:fb0591 L18:fb0591 (30591)
Track 20 Field 0 WhiteFlag:0 L16:000000 L17:ab0590 L18:ab0590
Track 20 Field 1 WhiteFlag:0 L16:000000 L17:fb0590 L18:fb0590 (30590)
Track 21 Field 0 WhiteFlag:0 L16:000000 L17:ab0589 L18:ab0589
Track 21 Field 1 WhiteFlag:0 L16:000000 L17:fb0589 L18:fb0589 (30589)
Track 22 Field 0 WhiteFlag:0 L16:000000 L17:ab0588 L18:ab0588
Track 22 Field 1 WhiteFlag:0 L16:000000 L17:fb0588 L18:fb0588 (30588)
Track 23 Field 0 WhiteFlag:0 L16:000000 L17:ab0587 L18:ab0587
Track 23 Field 1 WhiteFlag:0 L16:000000 L17:fb0587 L18:fb0587 (30587)
Track 24 Field 0 WhiteFlag:0 L16:000000 L17:ab0586 L18:ab0586
Track 24 Field 1 WhiteFlag:0 L16:000000 L17:fb0587 L18:fb0587 (30587)

Tuesday, July 30, 2013

Reverse 3X playback on the Sony LDP-1450 explained in detail

Since Time Traveler plays backward at 3X, I decided to gather some details about how this works on the Sony LDP-1450.  I have collected data on both what fields are displayed and also when the reverse playback takes place.

Here are some screenshots showing the transition from 1X forward to 3X backward, along with decoded VBI picture numbers.  The blue window on the right represents the serial port commands sent to the player and the responses received from the player (updated as close to real-time as possible).  I used a camcorder to record them side by side.

Here is a list of the VBI picture numbers before, during, and after the transition from 1X forward to 3X reverse.  The text in green is what is represented by the screen shots.

Track 659 Field 1 WhiteFlag:0 L16:000000 L17:fb0659 L18:fb0659 (30659)
Track 660 Field 0 WhiteFlag:0 L16:000000 L17:ab0659 L18:ab0659
Track 660 Field 1 WhiteFlag:0 L16:000000 L17:fb0660 L18:fb0660 (30660)
Track 661 Field 0 WhiteFlag:0 L16:000000 L17:ab0660 L18:ab0660
Track 661 Field 1 WhiteFlag:0 L16:000000 L17:fb0661 L18:fb0661 (30661)
Track 662 Field 0 WhiteFlag:0 L16:000000 L17:ab0661 L18:ab0661
Track 662 Field 1 WhiteFlag:0 L16:000000 L17:fb0662 L18:fb0662 (30662)
Track 663 Field 0 WhiteFlag:0 L16:000000 L17:ab0662 L18:ab0662
Track 663 Field 1 WhiteFlag:0 L16:000000 L17:fb0663 L18:fb0663 (30663)
Track 664 Field 0 WhiteFlag:0 L16:000000 L17:ab0663 L18:ab0663
Track 664 Field 1 WhiteFlag:0 L16:000000 L17:fb0664 L18:fb0664 (30664)
Track 665 Field 0 WhiteFlag:0 L16:000000 L17:ab0664 L18:ab0664
Track 665 Field 1 WhiteFlag:0 L16:000000 L17:fb0665 L18:fb0665 (30665)
Track 666 Field 0 WhiteFlag:0 L16:000000 L17:ab0665 L18:ab0665
Track 666 Field 1 WhiteFlag:0 L16:000000 L17:fb0666 L18:fb0666 (30666)
Track 667 Field 0 WhiteFlag:0 L16:000000 L17:ab0666 L18:ab0666
Track 667 Field 1 WhiteFlag:0 L16:000000 L17:fb0667 L18:fb0667 (30667)
Track 668 Field 0 WhiteFlag:0 L16:000000 L17:ab0667 L18:ab0667
Track 668 Field 1 WhiteFlag:0 L16:000000 L17:fb0668 L18:fb0668 (30668)
Track 669 Field 0 WhiteFlag:0 L16:000000 L17:ab0668 L18:ab0668
Track 669 Field 1 WhiteFlag:0 L16:000000 L17:fb0668 L18:fb0668 (30668)
Track 670 Field 0 WhiteFlag:0 L16:000000 L17:ab0666 L18:ab0666
Track 670 Field 1 WhiteFlag:0 L16:000000 L17:fb0665 L18:fb0665 (30665)
Track 671 Field 0 WhiteFlag:0 L16:000000 L17:ab0663 L18:ab0663
Track 671 Field 1 WhiteFlag:0 L16:000000 L17:fb0662 L18:fb0662 (30662)
Track 672 Field 0 WhiteFlag:0 L16:000000 L17:ab0660 L18:ab0660
Track 672 Field 1 WhiteFlag:0 L16:000000 L17:fb0659 L18:fb0659 (30659)
Track 673 Field 0 WhiteFlag:0 L16:000000 L17:ab0657 L18:ab0657
Track 673 Field 1 WhiteFlag:0 L16:000000 L17:fb0656 L18:fb0656 (30656)
Track 674 Field 0 WhiteFlag:0 L16:000000 L17:ab0654 L18:ab0654
Track 674 Field 1 WhiteFlag:0 L16:000000 L17:fb0653 L18:fb0653 (30653)
Track 675 Field 0 WhiteFlag:0 L16:000000 L17:ab0651 L18:ab0651
Track 675 Field 1 WhiteFlag:0 L16:000000 L17:fb0650 L18:fb0650 (30650)
Track 676 Field 0 WhiteFlag:0 L16:000000 L17:ab0648 L18:ab0648
Track 676 Field 1 WhiteFlag:0 L16:000000 L17:fb0647 L18:fb0647 (30647)
Track 677 Field 0 WhiteFlag:0 L16:000000 L17:ab0645 L18:ab0645
Track 677 Field 1 WhiteFlag:0 L16:000000 L17:fb0644 L18:fb0644 (30644)
Track 678 Field 0 WhiteFlag:0 L16:000000 L17:ab0642 L18:ab0642
Track 678 Field 1 WhiteFlag:0 L16:000000 L17:fb0641 L18:fb0641 (30641)
Track 679 Field 0 WhiteFlag:0 L16:000000 L17:ab0639 L18:ab0639
Track 679 Field 1 WhiteFlag:0 L16:000000 L17:fb0638 L18:fb0638 (30638)
Track 680 Field 0 WhiteFlag:0 L16:000000 L17:ab0636 L18:ab0636
Track 680 Field 1 WhiteFlag:0 L16:000000 L17:fb0635 L18:fb0635 (30635)
Track 681 Field 0 WhiteFlag:0 L16:000000 L17:ab0633 L18:ab0633
Track 681 Field 1 WhiteFlag:0 L16:000000 L17:fb0632 L18:fb0632 (30632)
Track 682 Field 0 WhiteFlag:0 L16:000000 L17:ab0630 L18:ab0630
Track 682 Field 1 WhiteFlag:0 L16:000000 L17:fb0629 L18:fb0629 (30629)
Track 683 Field 0 WhiteFlag:0 L16:000000 L17:ab0627 L18:ab0627
Track 683 Field 1 WhiteFlag:0 L16:000000 L17:fb0626 L18:fb0626 (30626)
Track 684 Field 0 WhiteFlag:0 L16:000000 L17:ab0624 L18:ab0624
Track 684 Field 1 WhiteFlag:0 L16:000000 L17:fb0623 L18:fb0623 (30623)


Playing backward seems to happen almost instantly.  For Dexter purposes, I should be able to make it happen instantly without any problem.

Also, when playing 3X reverse, complete tracks are not displayed, but instead the laserdisc player is skipping backward 3 fields every field.  This means that the picture will most likely always be interlaced with two unrelated frames when playing in this mode.

Saturday, July 27, 2013

Had a LAN party last night

I decided to take advantage of the awesome Utah summer nights and host an out door LAN party.  We had a blast!

Saturday, July 20, 2013

Working on Time Traveler in Dexter

Well, we went to my wife's family's cabin and to my pleasant surprise, they've installed DSL, which is pretty dang impressive considering how remote this place is.  Up/down speeds are about 1 megabit which is better than the dial-up I feared they had when I first heard that they had internet.

At any rate, I furtively brought my Dexter-related hardware with me on this trip (because why wouldn't you want to work on Dexter while on vacation??) and had a chance to do some work this morning while the rest of the family slept in.

I decided to try to fix the problems with Time Traveler (which basically means adding the reverse-playback commands that I have not added).  I am sure glad that I designed the .LDIMG file format with reverse-playback in mind because now it is really paying off.  It seems that if you die a few times in time traveler, it will issue a 0x4B command which means playback at 3X speed.  This is even if you don't use the time reversal cube!  And since I am dying left and right (possibly an emulation bug in Daphne), I can't play the game for very long without triggering this.

So, I am now working on adding the "play backward at 3X" command.

Monday, July 15, 2013

Electrohome G07 cap kit not working out quite right

I've been working on replacing the capacitors in my old Dragon's Lair Electrohome G07 arcade monitor.

I finished my soldering work and put my monitor back into my Dragon's Lair.  The good news is that nothing blew up when I powered it on.  The bad news is that it only displays a single (bright) horizontal line in the middle of the screen:

Anyone know where I can go from here to troubleshoot?

Sunday, July 14, 2013

CAX trip video

I went to the San Francisco, California area last week.

I was finally able to test Dexter on Dragon's Lair 2 and Time Traveler.  Verdict?  Dragon's Lair 2 works as well on a real machine as it does on Daphne so I now no longer need a real machine to test it on :)
Time Traveler is calling some LDP-1450 commands that I have not yet implemented so I've got more work there.
We also spent quite a bit of time on Freedom Fighter. It did not work unfortunately.

Here are some Dexter-related videos that I took:

Wednesday, June 26, 2013

VBI Injection v2: Soldering on SMT version of ATMega 328p

A new PCB arrived today.  This is the second attempt at VBI injection which uses a video multiplexer instead of an op-amp.  I soldered on the hardest component first.

Step one was just to get solder on all the pads, even if the pins are connected when they are not supposed to be.  Here you can see I schlopped a bunch of solder on and joined them all.

Step two was to use the solder wick to remove the connections.  The finished soldering job looks pretty pro if I do say so myself :)

Tomorrow, I hope to solder on the rest of the components.

I have to say, soldering on this ATMega 328p was a lot easier than I thought it was going to be.  The hardest part is getting the pins lined up to the pads before beginning.  I also did make the pads extra long to make my job easier.

Wednesday, June 19, 2013

SD card CRC algorithm explained in detail

Recently, someone asked me to explain the SD card's CRC algorithm.  I had forgotten a lot so I was not able to tell him much but I got curious and decided to document exactly how it works here on this blog post so I can refer to it later.

First of all, the official SD card documentation (in section 4.5 as of the time of this post) briefly describes the CRC algorithm and gives a few examples.  Unfortunately, its description assumes that the reader is a math major in a masters program at a prestigious university rather than provide a simple algorithm that would make sense to a novice software developer :)

The wikipedia article ( ) is a bit more friendly and gives a helpful example.

SD cards use two CRC algorithms: one is called CRC7, the other is called CRC16.  I will explain CRC7 here.  CRC16 is the same except its "polynomial" is different as is the amount of padding that the initial number must receive.

The polynomial for CRC7 is 0x89; the polynomial for CRC16 is 0x1021 which is based upon a standard called CRC-CCITT.  More about that here.

The CRC7 algorithm works like this:

- Pretend that the data buffer you want to compute CRC7 from is a really huge number (big endian).
- Take this number and shift it left the number of bits that the result will have, in this case 7. (this seems to be standard for most CRC algorithms)
- Find the first bit (starting from the left) that is a 1 in the number and XOR that with the polynomial (where the polynomial is shifted left enough times that its highest bit lines up with the 1 that you found, I will provide an example later)
- Repeat the last step until the original number of bits (before you shifted it left by 7) are all 0's.
- The CRC result will be in the bottom 7 bits at this point.

Here is an example of how to get the CRC7 result that the SD card specs say you should get.

Saturday, June 15, 2013

LDP sniffer board arrived

The LDP sniffer board arrived today!  Some initial tests with the multimeter show that it is wired properly.  I will have to do some more extensive tests later and also solder it together.  It shouldn't take too long.

Thursday, June 13, 2013

Injecting data into VBI section of NTSC signal

You think you're in the middle of a hurricane now?  It's time to... INJECT DATA INTO THE VBI SECTION!

First of all, if NTSC displays 60/1.001 fields per second (about 59.94), and it displays 262.5 lines per field, then this means that one line is displayed at a rate of (60*262.5)/1001 kHz, or 15.734 kHz.  Taking the reciprocal of this value gives us the period, 0.063555 milliseconds or 63.555 microseconds if I've done my math correctly.

According to the laserdisc standard, a picture number stored in the VBI will span 48 microseconds because each bit is 2 microseconds long and that includes a transition from black to white or white to black.  See for more information.

So my task is to figure out how many microseconds to delay after starting the proper line and then do a white/black transition every 1 microsecond as exact as I can.  I probably will need to completely unroll a loop in assembly language on the AVR to avoid drift.

Here is a screenshot from my scope showing the first 19 lines of an NTSC field (generated from the raspberry pi).

So what is the easiest way to find where line 19 starts?  Well, the LM1881 does have a pin called Composite Sync which may be able to help.

This shows the CSYNC signal.  As you may be able to see, the signal is weak during the vsync pulse but strong everywhere else (this actually surprises me).  My conclusion is that due to the relatively unreliable pulse the occurs during vsync, the CSYNC pulse cannot be relied upon exclusively to find where the proper line number starts.

My current approach is to wait for VSYNC (and/or FIELD transition) and then reset the AVR's cycle counter.  Then on every CSYNC pulse, check the elapsed cycle count to determine whether I am on the correct line.

This actually seems to work pretty well (so far).

I was able to successfully isolate line 11 and inject a little white bar into it.

Here is how it looks when captured in VirtualDub:

In short, this is very promising!  With some careful work I should be able to start putting in frame numbers on lines 17 and 18!

Wednesday, June 12, 2013

Successfully overlaid some white lines on video signal

Looks like the VBI injector is working!  Just as a test I tried to overlay some white lines at an arbitrary spot on the video signal and it worked!!

The next step is to hand craft some assembly language AVR code where the cycle count is precise so I can put the VBI info on the correct lines.

Video signal working on VBI Injector board!

It turns out I had the incoming video jack soldered on incorrectly (that's what I get for not reading the datasheet).  I removed it, and a few SMT resistors this morning to try to troubleshoot and found that absolutely no current was going through the video jack pins.  At first I thought I had a defective port, then realized it was simply that the jack had a shunt pin in addition to a signal pin.  Hehehehe.

I was hoping to post another picture in addition to some screenshots from my scope, but I ran out of time.  I soldered up the little arduino board both to my VBI injector board and to 5 places on the dexter board (RX1, TX1, CSYNC, VSYNC, and FIELD).  The gain on the VBI injector's amp possibly needs to be increased but my capture card is still decoding the picture properly so it is pretty close.

Assuming everything else works, all I need to do now is write the AVR program to inject the VBI.

Tuesday, June 11, 2013

Finished soldering but it doesn't work (yet)

I ran out of time to troubleshoot this, but the LM1881 is not generating any vsync pulses so it appears that the video signal is not making it to the LM1881 properly.  I was able to program the ATMega328P on the arduino mini and even get DebugWire working on it so that is a good thing.

Sunday, June 9, 2013

Almost done soldering the VBI Injector board!

The dirty gunk around the center of the board is all of the flux that I schlopped on it in order to solder on the tiny surface mount part that you can see in the lower middle.  This was my first time using flux.  It turns out that I didn't need to use the soldering wick at all.  I just schlopped on a bunch of flux and kept tapping the soldering iron to the bridges/shorts until they went away (as I saw demonstrated in a youtube video).  I also used a headset with a magnifying lens that I got off amazon which was pretty handy.  Oh, and I held it in place with a pair of tweezers that got from Sparkfun.  My wife held down the PCB as I worked.  I tested the pins with a multimeter and they all are going to the right place so I am pretty excited (and also a little surprised).  This makes me _very_ confident in trying to hand solder a Dexter rev3 board with all SMT parts on it (including the ATMega644P).

Yes, I too, can solder tiny surface mount parts! (with my wife's help)

VBI Injector board PCB v1 has arrived!

I look forward to soldering on the tiny surface mount part in the middle of the injector board!

Friday, June 7, 2013

Does the Raspberry Pi cut off the left and right sides when in TV-out mode?

The topic says it all. Does it, or doesn't it? I whipped up a quick test video (played a quick 15 seconds of Left 4 Dead in order to get a source that had no artifacts in it) and made some comparisons.

Original image rendered from the game and cropped by me:

Converted to .ldimg file, and rendered (using composite NTSC) by Raspberry Pi:
Now let's examine one place:

Here is the original.  Notice the border on the right side is dark green with a grey patch of a zombie's shirt.

Here is the pi version.  It's a little hard to tell, but the right side is completely black and the grey patch from the zombie's shirt is nowhere to be seen.


The pi does not shrink a source 720x480 image but instead overwrites the left and right borders with black.  This means that discs that are captured in 720x480 should be re-encoded "as is" without getting rid of the black borders.

Differences in .ldimg encoding quality

When encoding .ldimg files, one can adjust the quality where 100 is maximum quality.  Here is the difference in file size depending on the quality:


Conclusion: Q95 is probably the minimum quality one can go without seeing any JPEG artifacts at all. However, the file size is a little too big. Q90 therefore is probably the "sweet spot" for all Dexter .ldimg encodes because, while one can see occasional JPEG artifacts, it still looks very good.

Tuesday, June 4, 2013

Dragon's Lair 2 using Dexter + Raspberry Pi

This video has a surprise conclusion :)

More optimizations required

Well, I got the JPEG decoding speed running very fast.  However, once I add in some game-generated video overlay, performance drops from 100 FPS down to 85 FPS.  This was a bit of a red flag for me since OpenGL is supposed to be, well, fast!

So I am making a goal to get performance back up to over 95 FPS (or better) with the overlay active.  I've found some areas of potential optimization that I can do.

UPDATE: Success!

Sunday, June 2, 2013

JPEG optimization success!

Well, it looks like I've reached my goal earlier than I expected!

By making the change to decode JPEG directly to a GLES2 texture, performance skyrocketed.

Decoding from memory buffer and rendering on screen:
100 FPS (up from 62 FPS) <-- GOAL REACHED

Only decoding JPEG:
140 FPS (up from 103 FPS)

Saturday, June 1, 2013

The first JPEG decoding optimization yields modest gains

I optimized the way that I pass the raw JPEG to the OpenMAX API to decode and I've got some modest gains already:

Decoding from memory buffer and rendering on screen:
66 FPS (up from 62 FPS)

Only decoding JPEG:
107 FPS (up from 103 FPS)

Yes, a 4 field per second boost!  Every little bit helps!

Friday, May 31, 2013

Optimizing JPEG decoder on Raspberry Pi

Months ago, I spent a lot of time getting the Raspberry Pi to perform fast JPEG decoding.

While it is fast, it is just barely not fast enough for what I want to do with Dexter so I am now going to spend another chunk of time optimizing it further.

Here are some current decoding speed statistics.  I used a high bitrate JPEG to stress it to its limit.


Decoding from a memory buffer and rendering on the screen repeatedly:
62 fps (_fields_ per second)

When disabling the fragment shader that handles interlacing the video:
63 fps

Without transferring the decoded image to GLES2 but leaving everything else the same:
80 fps

Transferring the decoded image but not rendering it:
77 fps

Only decoding the JPEG, not transferring it or rendering it:
103 fps


- Transferring the decoded image costs about 20 fps
- Rendering the image costs about 20 fps (but exercising the fragment shader only costs 1 fps so this is a good sign since I rely on it).  It's possible I could optimize the vertex shader to reduce rendering speed.

- If I can decode JPEG directly to a texture then I don't have to transfer it in memory (this may boost up to 20 fps)
- Rendering performance may be running sub optimally
- I may be able to increase JPEG decoding speed by reducing some internal memcpy's I am doing right now and increasing OpenMAX input buffer size

- Increase overall decoding/rendering speed from 62 fps to over 100 fps if I can manage it.  I think bare minimum I will need to get it over 80 fps to give me some breathing room.

Wednesday, May 29, 2013

Laserdisc frame number (VBI) injector board

Not to rest on our laurels, Warren and I have been hard at work at solving the problem of making Dexter replace the PR-8210 and PR-8210A for essential games such as Cliff Hanger.

The problem: games that used the PR-8210 had to read the laserdisc frame number from the video signal itself.  The frame number was encoded during the Vertical Blanking Interval.

Our proposed solution: Using the LM1881 chip to detect when the VBI starts, we are going to basically generate frame numbers in real-time using an auxilliary AVR microcontroller, some resistors and capacitors, and an "op-amp".

This prototype will hook up to an AVR ATMega 328p (which itself will be located on a mini arduino board).  This in turn will talk to the current Dexter rev2 board via TX/RX serial lines and also the LM1881 lines which are already on the Dexter rev2 board.

Assuming we are successful, we will put all of this on the final Dexter board.

In summary, this prototype board is completely awesome!

The ultimate laserdisc player interface sniffer

In our quest to make Dexter as accurate as possible, Warren and I have designed a PCB to make it easy to sniff laserdisc I/O.  Behold!

It will start getting fabricated tomorrow and I should have it in my hands in about 15 days.

It can sniff 25-pin, 24-pin, AND the 40-pin Cube Quest connectors.  In other words, pretty much every laserdisc player interface out there except for the PR-8210 one but we can make that one by hand pretty easily anyway.

Wednesday, May 22, 2013

My "ultimate" pseudo-veggie shake

Ok, here is my fine-tuned pseudo-veggie shake recipe (I say pseudo because it is mostly fruit hehehe).  But it tastes great!

- 1 banana (120 g)
- 1 scoop (36 g) of CytoSport chocolate protein powder (you can get a huge bag of this at Costco)
- 50 g of spinach leaves
- 100 g of fresh strawberries
- A little bit of water (like half of a cup) so it will blend

Blend that all together and consume.  Tastes great, you can hardly taste the spinach due to the strawberries and banana.

Nutrition facts:
289 calories
30 g protein
40 g carbs
2 g fat

Conclusion: tasty and healthy :)

Friday, May 17, 2013

LDP-1450 accurate latencies

Ok, as "promised", here are more accurate latency results from sending a wide range of commands to the LDP-1450.

-> 67
<- 80 (3.373208 ms latency)
<- 0 (4.357307 ms latency)
<- 10 (5.499495 ms latency)
<- 0 (6.485570 ms latency)
<- 1 (7.471316 ms latency)
-> 56
<- a (3.495397 ms latency)
-> 41
<- a (3.493421 ms latency)
-> 3f
<- a (3.364645 ms latency)
-> 51
<- a (3.039905 ms latency)
-> 43
<- a (3.705852 ms latency)
-> 30
<- a (3.709145 ms latency)
-> 30
<- a (3.660072 ms latency)
-> 30
<- a (3.527344 ms latency)
-> 30
<- a (3.491774 ms latency)
-> 31
<- a (3.390663 ms latency)
-> 40
<- a (3.530967 ms latency)
<- 1 (539.072661 ms latency)
-> 3a
<- a (3.681150 ms latency)
-> 4f
<- a (4.550306 ms latency)
-> 3a
<- a (3.925529 ms latency)
-> 80
<- a (3.112362 ms latency)
-> 0
<- a (3.596178 ms latency)
-> 0
<- a (3.556985 ms latency)
-> 0
<- a (2.892356 ms latency)
-> 0
<- a (3.356082 ms latency)
-> 80
<- a (3.281648 ms latency)
-> 2
<- a (3.511206 ms latency)
-> 0
<- a (3.713098 ms latency)
-> 81
<- a (3.738458 ms latency)
-> 80
<- a (3.574441 ms latency)
-> 1
<- a (3.658096 ms latency)
-> 0
<- a (3.708157 ms latency)
-> 3a
<- a (3.357399 ms latency)
-> 0
<- a (3.457851 ms latency)
-> 3f
<- a (3.262875 ms latency)
-> 3f
<- a (3.031013 ms latency)
-> 0
<- a (4.275299 ms latency)
-> 3f
<- a (3.811244 ms latency)
-> 3f
<- a (3.505936 ms latency)
-> 58
<- a (2.952298 ms latency)
-> 3f
<- a (3.203922 ms latency)
-> 3f
<- a (3.450935 ms latency)
-> 1a
<- a (3.264522 ms latency)
-> 60
<- 30 (3.444018 ms latency)
<- 30 (4.414285 ms latency)
<- 30 (5.409253 ms latency)
<- 30 (6.575483 ms latency)
<- 34 (7.694616 ms latency)
-> 60
<- 30 (3.528991 ms latency)
<- 30 (4.659980 ms latency)
<- 30 (5.786030 ms latency)
<- 30 (6.786267 ms latency)
<- 34 (7.771025 ms latency)
-> 60
<- 30 (3.416682 ms latency)
<- 30 (4.394853 ms latency)
<- 30 (5.550215 ms latency)
<- 30 (6.544853 ms latency)
<- 34 (7.667280 ms latency)
-> 60
<- 30 (3.763159 ms latency)
<- 30 (4.888549 ms latency)
<- 30 (5.886811 ms latency)
<- 30 (6.892647 ms latency)
<- 34 (7.876747 ms latency)
-> 60
<- 30 (3.121913 ms latency)
<- 30 (4.273981 ms latency)
<- 30 (5.235026 ms latency)
<- 30 (6.380178 ms latency)
<- 35 (7.394577 ms latency)
-> 60
<- 30 (3.266169 ms latency)
<- 30 (5.144125 ms latency)
<- 30 (6.146010 ms latency)
<- 30 (7.120887 ms latency)
<- 35 (8.266697 ms latency)
-> 60
<- 30 (3.746033 ms latency)
<- 30 (4.704113 ms latency)
<- 30 (5.875613 ms latency)
<- 30 (6.868605 ms latency)
<- 35 (7.844141 ms latency)
-> 60
<- 30 (3.259253 ms latency)
<- 30 (4.989989 ms latency)
<- 30 (6.025467 ms latency)
<- 30 (7.126486 ms latency)
<- 35 (8.107292 ms latency)
-> 60
<- 30 (4.134995 ms latency)
<- 30 (5.259398 ms latency)
<- 30 (6.250743 ms latency)
<- 30 (7.390955 ms latency)
<- 36 (8.355622 ms latency)
-> 60
<- 30 (3.297787 ms latency)
<- 30 (5.113496 ms latency)
<- 30 (6.050827 ms latency)
<- 30 (7.068850 ms latency)
<- 36 (8.166246 ms latency)
-> 60
<- 30 (3.666988 ms latency)
<- 30 (4.793038 ms latency)
<- 30 (5.810731 ms latency)
<- 30 (6.772105 ms latency)
<- 36 (7.937018 ms latency)
-> 60
<- 30 (3.270121 ms latency)
<- 30 (4.571385 ms latency)
<- 30 (5.493566 ms latency)
<- 30 (6.628508 ms latency)
<- 36 (7.651141 ms latency)
-> 60
<- 30 (3.243444 ms latency)
<- 30 (4.931365 ms latency)
<- 30 (5.927980 ms latency)
<- 30 (6.903845 ms latency)
<- 36 (8.052290 ms latency)
-> 60
<- 30 (3.025084 ms latency)
<- 30 (3.986788 ms latency)
<- 30 (5.479734 ms latency)
<- 30 (6.634107 ms latency)
<- 37 (7.628746 ms latency)
-> 60
<- 30 (3.613634 ms latency)
<- 30 (4.893819 ms latency)
<- 30 (5.892410 ms latency)
<- 30 (6.981243 ms latency)
<- 37 (8.023637 ms latency)
-> 60
<- 30 (3.489798 ms latency)
<- 30 (4.636926 ms latency)
<- 30 (6.012623 ms latency)
<- 30 (7.005614 ms latency)
<- 37 (8.164599 ms latency)
-> 60
<- 30 (3.698606 ms latency)
<- 30 (5.165204 ms latency)
<- 30 (6.315955 ms latency)
<- 30 (7.441675 ms latency)
<- 37 (8.440265 ms latency)
-> 60
<- 30 (3.509559 ms latency)
<- 30 (4.642525 ms latency)
<- 30 (5.877589 ms latency)
<- 30 (6.980254 ms latency)
<- 38 (7.978845 ms latency)
-> 60
<- 30 (3.158142 ms latency)
<- 30 (4.444914 ms latency)
<- 30 (5.439882 ms latency)
<- 30 (6.560662 ms latency)
<- 38 (7.562546 ms latency)
-> 60
<- 30 (3.484528 ms latency)
<- 30 (4.638902 ms latency)
<- 30 (5.766598 ms latency)
<- 30 (6.768482 ms latency)
<- 38 (7.761474 ms latency)
-> 60
<- 30 (3.521745 ms latency)
<- 30 (4.514737 ms latency)
<- 30 (5.759681 ms latency)
<- 30 (6.766177 ms latency)
<- 38 (7.761144 ms latency)
-> 60
<- 30 (3.642287 ms latency)
<- 30 (4.644501 ms latency)
<- 30 (5.770221 ms latency)
<- 30 (7.167984 ms latency)
<- 39 (8.256158 ms latency)
-> 60
<- 30 (3.523392 ms latency)
<- 30 (4.509138 ms latency)
<- 30 (5.601923 ms latency)
<- 30 (6.736535 ms latency)
<- 39 (7.748958 ms latency)
-> 60
<- 30 (3.021461 ms latency)
<- 30 (3.989752 ms latency)
<- 30 (5.146102 ms latency)
<- 30 (6.270175 ms latency)
<- 39 (7.226279 ms latency)
-> 60
<- 30 (3.283624 ms latency)
<- 30 (4.218980 ms latency)
<- 30 (5.300238 ms latency)
<- 30 (6.295205 ms latency)
<- 39 (7.419279 ms latency)
-> 60
<- 30 (3.112033 ms latency)
<- 30 (4.269700 ms latency)
<- 30 (5.268291 ms latency)
<- 31 (6.262929 ms latency)
<- 30 (7.392601 ms latency)
-> 60
<- 30 (2.966131 ms latency)
<- 30 (4.109965 ms latency)
<- 30 (5.127658 ms latency)
<- 31 (6.102865 ms latency)
<- 30 (7.251639 ms latency)
-> 60
<- 30 (3.390005 ms latency)
<- 30 (4.489706 ms latency)
<- 30 (5.351616 ms latency)
<- 31 (6.505660 ms latency)
<- 30 (7.490089 ms latency)
-> 60
<- 30 (3.264522 ms latency)
<- 30 (4.266406 ms latency)
<- 30 (5.273560 ms latency)
<- 31 (6.394340 ms latency)
<- 30 (7.389308 ms latency)
-> 60
<- 30 (3.246408 ms latency)
<- 30 (4.246645 ms latency)
<- 30 (5.392126 ms latency)
<- 31 (6.390388 ms latency)
<- 31 (7.516437 ms latency)

It seems that most commands respond after about 3.5 ms (round trip).  The inquiry commands have a 1ms space between them.