If I could save time in a bottle
The first thing that Id like to do
Is to save every day
Till eternity passes away
Just to spend them with you
This time, starting to discuss my version 2 of the ISR code, I chose the lyrics from an older song, “Time in a bottle” by Jim Groce. It feels quite like the new spec for version 2 ISR. That’s exactly what the ISR is: a series of bottled (or canned, if you prefer) prescribed time. You may check out the code of the current version of the ISR here. Lots of unfinished things still lurk around, but the general code’s shape and organization are rather close to final, unless something serious comes up.
I am not going to describe in detail what the code does here. The reasoning is much similar to my original conception. There are four “periods” as spanned by the outer (in the sense of nested for-loops, because in its actual location in the code it’s an inner one) counter cnt4, and each period contains 8 PCLK full-cycles, spanned by the “inner” counter cnt8. Each 8-cycle train is carefully profiled (and painfully debugged) to take exactly 375 TCy’s, for a full 32-PCLK-cycle train that takes 1500 TCy’s, or equivalently, 125 microseconds (which yields exactly 256kHz).
Special actions are taken when cnt8 loops over to zero and for the whole 8-cnt8-cycle train when cnt4 is zero. During the first 8 cnt8 cycles of the 32-PCLK-pulse train, PCM audio I/O between the PIC and the 3210 takes place. When cnt4=1 and cnt8=0, the DRX (sent from the PIC to the 3210) and DTX (received by the PIC from the 3210) bytes are placed in an 8-byte buffer (actually it’s a 16-byte buffer, and data start to be placed with an initial offset of 8 ) directly in USB-reachable memory. This yields tremendous time savings because the very same buffers are then directly transmitted and received over USB (when 8 bytes have been collected -for DTX- or drained out -for DRX- and) at time cnt4=2 and cnt4=3, respectively. One more change from the original version is that FSYNC is now pulsed when cnt4=3 and cnt8=7, that is, at the 32nd cycle.
Happily, at 256KHz, filling in an 8-byte buffer with the above schedule takes exactly one millisecond. This is veeeery convenient, because this is exactly the period at which a USB host sends Start-of-Frame (SOF) microframes to the board. A few nanoseconds after the SOF, the board responds with an isochronous IN (from the board to the host) USB packet, which contains 8 bytes’ worth of PCM data. That is, 1 millisecond worth of data gets transmitted every 1 millisecond, and this happens synchronously with the 3210’s clock and the USB timing SOF signal. Couldn’t hope any better, could I?
Using directly the USB memory as buffers is feasible only thanks to a buffering technique that the PIC supports, called “Ping-Pong buffering”. In Ping-Pong buffering mode, the PIC uses even- and odd-rank Buffer Descriptors for each endpoint (in each direction, IN or OUT). Then, one set of IN/OUT transactions uses the even buffers, the next one uses the odd ones, and so on. This complicated things a bit in my code, but not very much. In return, one gets a tremendous speedup, because useless memory copies are avoided altogether. I felt very lucky that this mode existed in the first place, because otherwise I am afraid that the time in the bottle would not suffice.
A nice trick too is the initial synchronization between the 3210 FSYNC and the USB signals. For that, I had to tweak a bit the USB firmware provided by Microchip. The default firmware works in polling mode, and checks asynchronously for various events. One of these events was the SOF interrupt. In order to synchronize the ISR and the SOF signal, I needed to have complete control of the SOF flag from within the ISR, so I had to comment out an instruction in Microchip’s USB firmware that resets the flag. Obviously, my handling SOF in the ISR renders useless some other parts of Microchip’s firmware too, like the provided SOF callback function.
Probably I need to mention that debugging this thing was a loong, painful experience. Finally I resorted to TMR3, which can also run, just like TMR1, at the pace of the program counter (Fosc/4). I fixed the value of TMR3 so that it read zero when the ISR started, and then moved a debugging block around the ISR to verify my expected ‘@+xx’ values. Finally, after everything looked OK, I copied the value of TMR3 onto the IN USB packet and made sure that each value I was receiving had the same offset from the previous one, and that offset was exactly 12000 as expected (just in case you are wondering, this took me three whole days to conceive, plan, implement, test, debug, correct, re-try, etc. — I would never like to do that again, never…).
Bottom line: although the song goes
But there never seems to be enough time
To do the things you want to do
Once you find them
, I think that this time I squeezed just enough time in the bottle to keep my board drinking forever PCM data. As of this writing, the actual audio functionality is yet untested, but I ‘ll test it soon and report back by updating this post. In the meantime, readers (?? if any…) can enjoy browsing through the TMR1 ISR code (GPL-licensed, so freely usable in their own project).
I just hope to see — that is, to hear — all this code working fine in practice. Otherwise, I risk winding up like the old, white-haired mad scientist from Episode 7 (was it?) of the Muppet Show, who prepares himself a series of weird-colored, steaming potions and drinks them one after the other, each time getting younger and younger, under the melody of “Time in a bottle” (definitely a must-see); until, of course, he drinks the last potion, which turns him back into an old man…