Close
0%
0%

Propeller S/PDIF Receiver

Uncovering Subchannel Secrets

Similar projects worth following
If you're interested in digital audio, you know what the S/PDIF standard is. Also, if you've ever used any kind of digital recorder to record an audio CD to (say) a DAT recorder, you know that the S/PDIF signal not only carries digital audio but also some extra data, such as markers that indicate the beginning of a new track, and information that prevents you from copying a tape that is a recording from a CD.

For another project (which will be revealed when it's the right time), I need to decode the data that gets sent along with the audio in an S/PDIF signal. There are many S/PDIF receiver chips and it's not too hard to analyze S/PDIF with an FPGA, but I wondered if it would be possible to do it with a Parallax Propeller. If it is, that would be great because I also want to visualize the data on a screen later on.

The details about the S/PDIF protocol are standardized worldwide as IEC-60958. Unfortunately the IEC asks ridiculous amounts of money (hundreds of dollars) for a copy of the standard. As a hobbyist, I don't have wads of cash that big lying around.

The good news is that the Bureau of Indian Standard makes their own version of the standard available for non-commercial purposes, and it can be found at:

In this project, I want to find a way to decode the information from Part 3 (I don't have any devices that use part 4), but before I can get there, I have to also implement hardware and software to implement part 1.

There are many descriptions of the S/PDIF standard online such as this one: http://www.epanorama.net/documents/audio/spdif.html. It has also been described many times in magazines such as Elektor. The following is a description in my own words of the most important parts of the S/PDIF standard, based on the information in the BIS documents.

Read more »

  • Progress?

    Jac Goudsmit4 days ago 0 comments

    This is what the Channel Status subchannel looks like in binary:

    The code that I used for this output prints a block counter, followed by the Channel Status subchannel in binary (chronological order). As you can see, it contains the expected data: 11000011 in the second byte, because I connected my Propeller to a Digital Compact Cassette recorder which uses category code 110 0001L and this is a prerecorded tape so L=1. The rest of the data is all zeroes, which is a little boring but this at least demonstrates that I understand how block decoding and subchannel demultiplexing works.

    To get this output, I created a quick-and-dirty copy-paste module based on the status channel decoder, which doesn't just copy the status channel bits but entire blocks of subframes. This is what full blocks might look like in hexadecimal (actually the following output only shows the subframes for the left channel):

    The Channel Status subchannel is bit 30 of each subframe, in case you want to dig through the hexadecimal and make sure it checks out :-)

    So as part of narrowing down the problem with my subchannel decoder which only produces zeroes, I've proven that the data is there and has the expected format. Obviously there's something wrong with the PASM code that extracts the bits and sends them to the hub.

    I must be overlooking something in the PASM code or I'm misunderstanding how Spin works to copy the data.

    Hmmm...

    More research is needed :)

    UPDATE: Now we're talking!

    I found out what the problem was with the subchannel decoder: there was a rogue JMP in there that made another instruction unreachable that was needed to increase the destination address of the rotate-instruction. So yeah I saw a lot of zeroes because I had bit storage in the cog initialized to 0 and the $C3 in the second byte wasn't showing up because the first longword (that has the $C3 in it) was overwritten by 5 other ones by the time I got to see it.

    I rewrote the status channel module to be more universal (it can now also be used for the user data channel) and it looks like that works. Yay!

    However: remember I was hearing horrible distortion in the audio generator sometimes, that I could eliminate by resetting the Propeller? It looks like this is not a problem in the audio module, but in the biphase decoder. Apparently sometimes (and with sometimes I mean: way too often) it somehow locks onto the signal in the wrong way. I'm going to have to fix that because it's crucial for that to work. I must have overlooked some sort of corner case. I'm pretty confident I can fix it with a change to the initialization of the biphase decoder and/or preamble detector.

  • I Added a Status Subchannel Demultiplexer

    Jac Goudsmit6 days ago 0 comments

    ...But it doesn't work.

    The Status Subchannel is a group of 192 bits that's continuously transmitted as part of the S/PDIF signal, interleaved with the audio. For every stereo sample, one bit of the status subchannel is added to the stream.

    There's supposed to be some interesting information in this subchannel, for example it can indicate what the type is of the device that the sound is coming from.

    But as you can see in the above screenshot, I'm not getting ANY data from my player. I'm not sure why.

    The User Data subchannel should prove more interesting for my "secret" final purpose of this project. For starters, it has twice as much data because there is a separate bit of information for each audio channel, and I know from looking at the logic analyzer earlier that there's definitely data in there. But when I changed the Assembly instruction that pulls the Channel Status bit from the subframes so that it puts the User Data bit (or any other bit for that matter), the output of the hex dump stays zero, though I've seen some glitches where I had some random data. That probably means I'm trashing memory somewhere...

    Hmmm...

    Well, it's night night time, I'll have to do a thorough code review tomorrow.

    PS: By the way, I decided that modifying the biphase decoder to write the subframes to a block of memory in the hub was too much work. The audio player is easiest to implement when it can just wait for the next subframe by checking PRADET, and it's not too difficult to implement the subchannel decoders that way too. And for other future purposes (CD+G decoders, I2S output generators, etc.) it's also not that hard to just wait for a single sample. Other advantages of this method are that the propagation delay stays low (one subframe delay instead of one block delay) and it keeps things easy for the Spin parts of the code, so I don't have to wrestle with partially filled circular buffers and other stuff. The buffer is one single longword and the synchronization method is the PRADET signal, and that's good enough for pretty much everything. If necessary, I can always use another cog to serialize each block of samples somehow.

  • Analog Audio Demo

    Jac Goudsmit06/18/2017 at 08:12 0 comments

    What's this, another change to the hardware? Well... Yes and no. I like the new Parallax FLiP for breadboard development but I wanted to do something today that actually makes the S/PDIF decoder do something that's (arguably) useful, so I connected a QuickStart board with a Human Interface Board for the Quickstart. The HIB board has filters and a connector to connect stereo headphones (just like the Propeller Demo board, by the way) and I wanted to write a small module that just grabs samples from the Biphase decoder and sends them to the headphones.

    It took me a while to get it to work, because apparently I had made a mistake and passed the pointer to a parameter variable (which was already a pointer) instead of passing the parameter directly.

    Because of this, I now have a sort-of sanity check that lets me know that the biphase data is decoded and processed correctly (well... at least the audio part of the subframes).

    The next step will be to change the Biphase decoder to write the data into a buffer instead of a single long word. This should come in useful for extracting subchannel data. And that's what this project is all about.

  • Biphase Decoder Working

    Jac Goudsmit06/12/2017 at 06:51 0 comments

    The idea that I presented in my previous log, to count pulses (instead of measuring time between pulses, or sampling for a second pulse in the middle of a bit) works great!

    The Biphase Decoder now consists of two cogs that take care of all the following:

    • Decoding the biphase bits in each subframe to regular binary values
    • Detecting the preamble to detect the start and end of each subframe
    • Decoding the preamble type to distinguish left and right channel samples
    • Decoding the preamble type to distinguish the first subframe of a block from all the other subframes
    • Storing a LONG word into the hub with all the information above, at the end of each subframe.

    That took quite a few smart tricks with Propeller Assembler (PASM). For example I found out that it was more efficient to set the channel output pin to 1 for the left channel and 0 for the right channel instead of the other way around, and I found out that it was more efficient to decode the biphase bits in one's complement.

    Let's have a look at some of the important parts of the code; for the full story, check out the source code on Github. The file biphasedec.spin has a lot of documentation at the top.

    Read more »

  • New Idea and a Change of Venue

    Jac Goudsmit06/03/2017 at 22:02 0 comments

      I haven't had time to work on this for a few days but I wanted to share a new idea of decoding the biphase input with the Propeller timers, and this picture:

      The photo shows a minor change of venue: I rebuilt the schematic on a different breadboard using the new Parallax FLiP module. This is a great new product ftom the producers of the Propeller (I'm not affiliated, just a fan as you may have noticed) that offers an entire Propeller circuit including USB to serial converter, 5 MHz crystal, and a great flexible power supply on a board that's the size of a DIP Propeller. They even had space to solder two LEDs onto pins 26 and 27 which can help greatly with debugging. Using the FLiP makes it much easier to put together a Propeller circuit because (unlike with the PE kit) you don't have to mess around with the power supply, the crystal, and/or a prop plug.

      Anyway, about that new idea I had: As I've mentioned before, the Propeller has to be able to process one bit of biphase data in 326ns if I want to make it work at 48kHz stereo sampling rate. That's about 6 assembler instructions. I already got it to (kinda) decode a signal by using two cogs (one for recovering the clock and one for measuring time) but I wasn't happy with the result.

      The last implementation of the biphase decoder counted flanks on the XORIN input which is better, but it was very difficult to get the timing right because of all the propagation delays, and I had the feeling that that wasn't going to be robust enough. For one thing, the timing would have to be adjusted to the input frequency, even if it changed only slightly.

      But earlier this week I had an epiphany: what if I just keep counting flanks from the beginning of a subframe to the end of a subframe, and never reset the counter? That would have some serious advantages!

      In the previous version of the code, I would read the timer/counter (PHSA register) at the start of every bit, and then reset it. But by the time that the timer actually gets reset, the next pulse is already almost coming in if the current bit is a 1. That's exactly what I didn't like about that code: I had to time the reset exactly right (within one 12.5ns Propeller clock cycle accuracy) so the timers wouldn't miss anything, and I had the feeling that this was pretty much impossible given the amount of jitter. That's not something I want the end user to have do (besides, it would probably be difficult to do while the system is running).

      I thought up a simple loop in PASM that goes about as follows:

      1. Set timer A to count positive edges on XORIN.
      2. Wait for a positive edge on XORIN using a WAITPxx instruction. This signifies the start of a new bit.
      3. Test the lsb of PHSA to find out if there was an odd or even number of transitions in the spdif input.
      4. Shift the odd/even result into a long word as a single bit.
      5. Check if a preamble was detected and jump out of the loop if so (I may do this a different way but that's not relevant for this discussion)
      6. By now, 6 instructions should have passed so jump back to the beginning of the loop (step 2). Alternatively, I may unroll the loop and just paste the above 32 times.

      At the end of the subframe, there are 32 bits of data available but each bit doesn't represent an actual data bit value but a record of the oddness of the total number of biphase flanks at the end of each bit time.

      On the SPDIF input, a zero-bit is represented as a single transition and a one-bit is represented as two transitions. So if the total number of transitions goes from even to odd or from odd to even in one bit-time, the encoded data bit must have been a 0 because one transition on the SPDIF line causes one pulse on the XORIN line and an odd number plus 1 is always an even number and vice versa. Similarly if the total number of positive edges on XORIN stayed even or stayed odd, the encoded data bit must have been a 1 because the oddness of a number doesn't change if you add (a multiple of) 2.

      I'll have to figure out if it's possible to process the...

    Read more »

  • I Got Bits, but...

    Jac Goudsmit05/31/2017 at 07:57 0 comments

    A quick update:

    I rewrote the code that regenerates the clock from the incoming SPDIF signal, and as you can see in the picture above (next to the RECCLK label), it's very steady now, and it runs at half the bit rate. So instead of generating one clock pulse (i.e. an up/down cycle) for each input bit, the RECCLK signal only changes once for each input bit.

    That means that the code in the data sampling cog(s) has to be repeated twice: first it waits for RECCLK to go high, then it reads a bit, then it waits for RECCLK to go low and reads another bit. This may look a bit sloppy but is not uncommon in Assembly programming: it's basically a partially unrolled loop. I may go one step further and unroll the entire loop for a single subframe (so the same code will be in the source file 32 times), if there's not enough time to get things done.

    The code uses a constant to delay the RECCLK signal by a variable number of clock cycles (it uses a timer in NCO mode). Basically:

    • The sync cog waits for the XORIN input (the "original" SPDIF xor'ed with the delayed SPDIF to detect flanks) to go high
    • It then starts a timer to toggle the RECCLK output
    • Then it waits until the output is actually toggled and starts the timer to toggle the RECCLK output again
    • After this, things start over from the top.

    I also added preamble detection code which correctly identifies preambles (see the PRADET trace) but needs a little work: I want to make it generate a single pulse that starts right when the first long input pulse is detected, and ends when the first bit of the next subframe comes in, but as you can see, it triggers multiple times. For now it's good enough to trigger my Logic Analyzer so I can record an entire subframe.

    I also implemented a small "debug monitor" cog that puts the data bits on a pin that's shown here as "DEBUG". As I mentioned in the previous log, the idea was to synchronize the data cogs on the recovered clock, and sample the XORIN pin at the time when RECCLK changes state. But this is actually a bad idea: The delay circuitry is very simple and there's a lot of jitter: I've seen recovered clock cycles that were more than 50ns too long or too short because of the jitter on the delay. That makes it pretty much impossible to get the timing right if I want to sample the signal right at the time when a second pulse comes in over XORIN.

    But the Propeller timers can be programmed to count negative or positive edges, so that the exact delay time in the external circuitry becomes a lot less critical. The Debug Monitor cog that I programmed, basically waits for the RECCLK signal to change, and then checks if the Propeller timer detected two negative edges in the last cycle. If so, it sets the DEBUG output to 1, otherwise it sets it to 0. As you can see, it correctly decoded the subframe in the middle (between the PRADET pulses) as (1)0000'0010'0110'1001'0001'0111'0001(0) (the 1 at the start and the 0 at the end are false readings because of the preamble, I'll deal with those later).

    What bothers me a bit though is that it was difficult to get the debug cog to work right: it's synchronized to the RECCLK with WAITPEQ/WAITPNE instructions but when I simply had the code read the data after each WAIT and then restart the timer, I couldn't get reliable results no matter how I set the delay for the sync cog. I had to basically:

    • Wait for the recovered clock RECCLK using WAITPEQ/WAITPNE
    • Wait for a little while longer using NOP
    • Check if there were 2 negative edges of XORIN
    • Reset the edge counter
    • Wait for the next edge of RECCLK.

    It bothers me that this way, the timing is partially based on the time it takes to execute the instructions. It means I'm doing something wrong or I need to use another timer in the debug cog (which may later become the data cog).

    I'll have to take a thorough look at the debug cog and analyze what's going on and where the delays are. For one thing, the timer that detects negative edges is delayed by one clock cycle.

    Another thing is that if I need...

    Read more »

  • Sending in the Big Guns

    Jac Goudsmit05/30/2017 at 08:44 0 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:

    • Unfortunately, some external circuitry is unavoidable. In the previous log I said I would like to get rid of it, but that's just impossible. The external circuitry converts the S/PDIF bi-phase signal of any polarity to a simple signal where each transition of the input is converted to a pulse. Then it's just a matter of timing the distances between those pulses to get the original binary stream.
    • One cog (let's call it the "sync cog") will be in charge of recovering the clock signal from the input, and will put that signal on a helper pin that can be used by other cogs for synchronization.
    • The same "sync cog" will also detect preambles and will generate output on another pin to synchronize the other cogs.
    • A "data cog" will use the output signals from the first cog to synchronize to the clock and read input bits. At the end of a subframe, it will store the 32-bit decoded word in the hub.
    • If I can't make the "data cog" fast enough to store data into the hub at the end of each subframe, there will be two "data cogs", one that reads the left subframe and one that reads the right subframe.
    • Once the data is in the hub, the subchannels can be demultiplexed and dumped to the serial port or to the screen. It will probably also be possible to use existing open-source modules to send the code out to a recorder or a playback device, after optionally modifying the data.

    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:

    • You can't let the code wait for a timer (only for the system counter and it takes three assembly instructions to set that up, and that's two too many for our purposes)
    • There are timer modes that automatically start a timer when e.g. an input pin is low or high, to measure how long a timer input has been in that state. Unfortunately, the code still has to babysit the timer: if you're measuring how long a signal is high or low, the only way to tell that the signal is no longer in that state is to test it with assembly instructions (either by testing the signal directly or by testing if the timer is counting; both of which are several instructions that I don't have time for in this project)
    • There are 2 timers per cog which is really useful (16 timers total is not too shabby), but that means that one cog doesn't have direct access to a timer's state in another cog. So to synchronize one cog with the timer of another cog, you have to sacrifice a pin and use it for output on one cog and input on another cog.
    • There are some "logic" modes which allow you to analyze two input pins in various ways, but unfortunately it's not possible to generate direct output from these modes: the code has to examine the timer registers to see if anything has been registered. Nice for slow events but unusable for our purposes.
    • The timer modes that let you measure the time that an input pin was low or high, allow you to provide an output pin but the output is always just a delayed version of the input, and the delay is always simply the input pin delayed by one clock cycle.
    • There are various timer modes that generate useful output, especially the NCO and DUTY modes which use bit 31 of the counter, or the carry flag of an adder to generate output. This can be used to generate a pulse of a predefined length or after a predefined delay.

    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...

    Read more »

  • Initial Setup

    Jac Goudsmit05/26/2017 at 05:56 0 comments

    I started out with the Propeller Education Kit. I added an RCA breadboard converter that I got from Parallax a while ago while working on Propeddle and L-Star (I don't think they carry the RCA plug breadboard converters anymore, that's a shame. But they still have the PE kit I think, and they just announced the new Propeller FLiP which makes breadboarding even easier).

    I also added a small circuit based on a schematic that I found online:

    The S/PDIF (coax) input signal is an unbalanced signal with about 0.5V peak-to-peak. It's terminated with a 75 ohm resistor conforming to the standard (75 ohm input impedance; note: the photo shows a 100 ohm resistor, that worked fine too). Then the signal goes through a capacitor which acts as a DC decoupler. Then the signal is pulled halfway between the positive and negative supply voltage of a 74HC04 inverter port using a voltage divider made of two 10K resistors (the photo at the top shows a potentiometer to adjust the input voltage but this turned out to be unnecessary). The first inverter acts as an inverting amplifier to digitize the signal so it can go into the Propeller.

    The two inverters that follow, with the R/C networks at their inputs, generate delayed versions of the signal at P0. I didn't run the numbers and I don't want to connect my oscilloscope to verify it (it's buried in the garage), but my source tells me the each of the ports-with-RC-circuits (between P0 and P1, and between P1 and P2) generates a 60ns delay, so a total of about 120ns.

    The highest frequency input that we want to accept is based on a 48kHz sample frequency. That means in the worst-case scenario, there are 48000 full frames of data per second on the input. Each frame is 2 subframes and each subframe is 32 bits so that's 48000 * 32 * 2 bits per second, that's a clock frequency of 3.072MHz. Since there can be up to two biphase bits per data bit, the minimum length of one biphase pulse may only be 162.7 ns (give or take, with jitter).

    So the delay is shorter than the shortest bit time, and that's important. By performing an XOR on the original input and its delayed version, you can get a signal that represents each flank in the original signal by a short pulse. This takes away a big problem with the decoding: in S/PDIF the level of the signal is irrelevant, but the Propeller has to know what the state of the signal is in order to wait for it to change. By XOR'ing the signal and its delayed (noninverted) version (and by keeping the delay under the length of the shortest expected input pulse length of 162ns), the signal that the software would have to deal with, is always LOW when the S/PDIF signal stays the same, and HIGH for 120ns when the input signal changes.

    During one bit clock of the original signal, two pulses can be seen on the output of the XOR for each "1" bit, and one pulse can be seen for each "0" bit. During preambles, the signal stays the same for 3 biphase bit periods so we won't see any pulses for longer than that.

    I ran a couple of tests with this, and tried to put some assembly code together to verify (sort of) that this was a usable concept.

    First I wrote a short PASM program to light up one LED when the signal was high and another LED when the signal was low. On an S/PDIF signal that was basically digital silence, the LEDS were equally bright, as expected.

    Then I wrote a short PASM program to test pins 0 and 2 at the same time, using the "TEST maskP2P0, INA WC" instruction where maskP2P0 was set to %101. This tests whether pins 0 and 2 are high, and sets the carry flag if the parity of the result is odd. In other words: it tests whether either pin 0 is low and pin 2 is high, or pin 0 is high and pin 2 is low (basically an XOR function). I then used the MUXC and MUXNC instructions to make one LED light up when the carry flag was set, and the other one when the carry flag was cleared. It was clear to see that one of the LEDs was brighter than the other.

    So that works! But now... How to write...

    Read more »

View all 8 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates