Sending in the Big Guns

A project log for Propeller S/PDIF Receiver

Uncovering Subchannel Secrets

Jac GoudsmitJac Goudsmit 05/30/2017 at 08:440 Comments

As I noted in the previous log, S/PDIF is way too much to handle for one cog of a Propeller. That's okay, I have 7 more cogs. But in projects like this where you have to divide the work over several different cogs, it's difficult to keep your head around things. At the time that I'm typing this, the plan is somewhat as follows:

I already noted that it was going to be necessary to mess around with the timers in the Propeller. Timers in the Propeller are incredibly useful -- as long as you just want to use them for output. For input tasks such as measuring time in a high-speed environment, the timers need a lot of help from the code:

When I started on the project, I had set up a simple circuit with a 74HC04 and some passive parts to amplify the input signal to the full range of CMOS, and to generate a slightly delayed version of that signal that could be used to detect flanks, regardless of the polarity of the original signal. With a WAITPEQ or WAITPNE instruction, it's easy to detect whether pins are equal or different (just test both pins at the same time and use the Carry flag to detect the odd parity). As I already mentioned in the previous log, that wasn't going to be fast enough by a long shot.

With timers, it's not possible to use two input pins at the same time. So I added a 74HC00 to my breadboard to make an XOR port, so I would have a low latency signal that indicated whether a flank (upgoing or downgoing) had been detected in the input signal. The signal on my breadboard is now roughly as follows:

I combined the two delay ports into one by putting the two 100pF capacitors in parallel. Of course now there's only one inverter between the first stage (called SPDIN for SPDIF IN in this picture) and the second stage (called SPDDEL for SPDIF DELAYED) so the polarity of the delayed signal is now reversed compared to the schematic in the previous log.

The three NAND ports that I added and the two NOT-gates generate an output that's HIGH when the SPDIN and SPDDEL signals are the same, which happens for a short time when the input changes polarity (remember there's an inverter so the delayed signal is different as long as the input doesn't change). By the way, it looks like I may not need the NAND ports after all. I'll regard that as an optimization so I won't worry about it for now.

I decided to send in the big guns and dig out my HP 16500C logic analyzer / oscilloscope (see the photo near the top of the log) from the garage, so I could play around with this. I bought it from eBay a few years ago and I don't use it often because I don't really have space for it. When I got it, it was noisy too, but I replaced the hard disk by a 64MB CF card and replaced the two loud fans with a single quiet one that still moves plenty of air to keep the system cool.

Having an oscilloscope and a logic analyzer allowed me to quickly try out a bunch of different approaches to recover the clock from the input (and generate a signal that I can hopefully use in a future "data cog" to read the data.

This piece of code sets up two timers, both in NCO (Numerical Controlled Oscillator) mode. It then strategically resets the timers when certain things happen.

  1. At the beginning of the loop, the program assumes that the P3 input (the XOR output of the circuit diagram above) is LOW.
  2. It waits until the pin goes HIGH, indicating that the input changed.
  3. Then it resets timer A which generates a pulse that is exactly the right size to end at the time when (during a bit with value 1) a second pulse comes in on XOR.
  4. Then it tests whether the time since the previous change in the SPDIF is long enough to indicate the start of a Preamble (all preambles start with a 3 biphase bit absence of changes in the SPDIF signal). The Z flag is cleared if a preamble was detected, or set if it wasn't.
  5. If this wasn't a preamble, clear the preamble timer (i.e. start measuring from now), wait for the end of the pulse on the XOR line if necessary, and the repeat the loop to wait for the next pulse.
  6. If this was a preamble, keep busy for a while, then do the same things that a non-preamble loop would do and repeat.

Obviously this could use some cleanup but the result can be seen in the photo of the logic analyzer screen: the code correctly generates clock pulses and a longer pulse indicating that a preamble was encountered.

I wrote some code to test whether another cog could successfully read the data by waiting for the recovered clock to go low and then sampling the XOR pin, but this didn't work. First of all, it took too many instructions (basically it can't get its work done on time). Second, there is not enough time to wait for the clock and then read the data: by the time the data-reading instruction is being executed, the input is gone.

In the next log I'll try to fix this by doing two things: