Biphase-Mark encoding and decoding - a short guide.
Biphase-Mark encoding is sometimes recommended as a communication method to get data from one place to another. This guide is intended to show how it works, how to code it and decode it, and how to recognise it.
What is it?
It's a method of encoding data onto either an audio signal, or on a logic-level signal path. Its characteristics include:
- self clocking; you don't need to know the data rate to decode it
- average DC is zero; capacitively coupled audio signals will still decode properly
- robust; it depends on phase-inversions to identify bits
- able to handle either synchronous or asynchronous data
- able to manage a wide range of data rates
- able to be safely decoded on changing data rates - e.g. from a spooling tape
The signal is defined such that there is a phase change in the level of the signal at the end of each bit. If the bit is a one, then there is a further phase change in the centre of the bit period. So, a continuing stream of zeros at a 1kbit/s data rate would be a 500Hz signal; a continuing stream of ones would be a 1kHz signal. Normal data would have a mixture of both; it has a very distinctive sound.
Biphase-mark encoding is widely used in the broadcasting industry for the distribution and recording of linear time-code; the audio signal is recorded on analogue video or audio tapes and can be read both during normal playback and while spooling quickly. It's also often distributed around studio areas as a source of time-of-day data; a reader can be plugged into a jack and the time can be immediately displayed.
Another surprisingly common place to find biphase-mark encoding is in the SP-DIF digital connection (and of course the closely related AES-EBU digital audio).
In the attached files, I have included the source code for an encoder - biphase.c - and a decoder - unbiphase.c. Rather than try and provide an example for AVR - where it can be implemented in so many different ways, depending on the type of AVR chosen, resources required by other parts of the program, and so on - I have provided generic C programs for a PC. They are written for GCC under Linux though they should work with minimal changes - if any - on any of the MS C compilers. The only point to note is that they require variables of int=32 bits, short=16 bits, and char = 8 bits.
It should be noted that neither of these programs is robust enough for production software. Both have far too little error checking, particularly when opening, reading from, or writing to files; neither behave properly in respect of the Microsoft RIFF format files.
Synchronous vs. Asynchronous
Synchronous data is presented as a continuous bitstream; immediately after one byte of data follows the next. There are no start or stop bits (though parity may be included in the bitstream), so some other mechanism must be used to synchronise the start of the data. Also, if the data input stops, the bitstream must continue. This is generally managed by having a 'sync byte' in the bitstream which is output whenever there is no 'real' data to output. The sync byte must be something which is unambiguous and cannot be mistaken for the real data; this therefore reduces the number of data byte options available in the main data - for example, on an eight-bit system, the code '0xFF' might be reserved as a sync byte. However, if there is the possibility of entering the data stream at a random position, you have to ensure that the data itself in any random position cannot simulate the sync byte - for example, the sequence '0x0F', '0xF0' would look like a sync byte.
Linear Time Code - as mentioned above - is designed to be read not only at multiple and varying speeds, but also forwards and backwards. To achieve this capability, a sixteen-bit sync block is included which cannot occur as a legal bit pattern elsewhere in the 80-bit packet, which is repeated every 25th of a second. The pattern in the sync block can be identified to give the direction of the data.
Asynchronous data is most familiar to most as the non-return to zero RS232 style data. The data can occur at any time, which implies an idle state on the line. On the RS232 format (ignoring the actual transmission levels) this is a '1'. To mark that data is starting, a start bit is used - a zero - followed by a number of data bits. After an optional parity bit, one or more stop bits - '1's - will restore the line to the idle level. As a result, each 8-bit byte of data takes a minimum of ten bit periods to transmit. Because the start bit can occur at random times, the line has to be sampled somewhat faster than the nominal data rate; normally, sixteen times. This enables the following bits to be sampled somewhere near their centre point for improved noise performance.
A disadvantage of such data is that the clock rates of the transmitter and receiver must be close. Once the two rates differ by around 5%, the sampling point of the last bit can slip to the previous or following bit, producing an error. This is a common problem with AVRs clocked on their internal uncalibrated clocks; either the clock must be calibrated in software or crystal or other high-accuracy clocks must be used. But because the bit timing is self-clocked on the biphase-mark coding, clock drift is not important; the data is guaranteed.
After some consideration, I decided to implement a non-synchronous format for my example. The main driver for this is that most people are more familiar with the format, and it provides a more complex example - though not as complex as a finished implementation might be.
My format - when writing to audio - therefore has a short period of '1's as an idle state, followed by the data itself in the one start bit, eight data bits (bit 0 first), no parity bit, and one stop bit form. After the last data, another short period of '1's is recorded - unnecessary in this instance but with a more complex system with say short frames of data containing some sort of error correction, providing an ideal inter-block idle state.
The audio format is mono WAVE format, 16,000 bits per second, sixteen bits per sample. Since I am recording a square wave, this is excessive but means I can perform other unpleasantries on the audio data - filtration, compression etc - with standard tools.
RIFF files are a Microsoft standard packaging stream for - usually - multimedia storage. They may contain audio, video, random data, and metadata, contained in chunks. The WAVE format is reserved for audio applications.
A chunk consists of a four-byte ascii header with the name of the chunk, four bytes (unsigned long) to give the length of the chunk data, and then the chunk data itself. A chunk can contain sub-chunks to any number or level, though it is unusual to get any deeper than three levels. Note that the WAVE ident does not have an immediate data length following it. Although MS refer to this as a chunk, my feeling is that it should be considered as part of the data - so the chunk consists of the WAVE identifier and a minimum of two subchunks - the fmt and data chunks.
The start of a minimal WAVE file looks like
RIFF <- the riff chunk ident xxxx <- the length of the RIFF chunk (usually the entire remainder of the file) WAVE <- the WAVE chunk ident fmt <- the format chunk identifier xxxx <- format chunk length .. .. <- data depends on the length and type of the format chunk data <- the data chunk identifier xxxx <- data chunk length .. .. <- many many data samples
The fmt chunk data describes the format of the data (PCM, MP3, or other compression), mono or stereo, bit rate, sample rate and so on. The audio data needs to be understood in the terms of this header. In this case, we're going for a straight-forward PCM mono signal at sixteen bits; hard to get wrong.
I should stress that the method I have used to create the file is not recommended by Microsoft (or me, really) in that all I do is preload the chunk structure as a single 'waveheader' structure. This produces a legal file, and I haven't come across any software which can't read it, but modern wave structures include extra fields. The file reader I provide in unbiphase.c can cheerfully read this format, but may break on other wave files - though it will not crash, you may find that you're treating the extra fields (or indeed, extra chunks) as audio data.
The official way to deal with these files is to use Microsoft's RIFF API; creating chunks on demand to write and 'walking the chunks' to find the chunks in which you are interested and ignoring the rest. However, that option is not immediately available in the GCC libraries, and would distract from the point of the examples.
Coding and decoding
Coding biphase mark data is simple; one needs to arrange a phase change at the bit rate and another phase change in the middle of that period, for the '1'. How that is arranged is up to the programmer. In this version, I've just used an integer number of audio samples to time the phase change. Alternate approaches might include timer interrupts every half a bit period, with a routine that decides whether or not a phase change is required at any particular interrupt. It can also be derived in hardware, using a couple of flip-flops.
Decoding is also reasonably simple, depending on whether you know the data rate and that it will be stable. If you do know it, then it's as easy as looking at the interval since the last phase change. If it's more than 3/4 of the bit period, you've just received a zero; otherwise, you
got half of a one. If you already had the first half, this is the second half and you can return a one; otherwise, flag this as a first-half received. Note that because you don't necessarily know the phasing of an arbitrary stream of ones, it's possible that the following half-cycle might be a zero; this unambiguously identifies the zero but data prior to that must be considered suspect.
If you don't know the bit rate, it's a little more complex. Now, you need not only the period of the current half-cycle but that of the previous half-cycle. If these two are the same (within 0.75 and 1.5 times) then we can safely assume that we have another of what we previously had... but we might not know yet what it is. If the current period is more than 1.5 times the previous, then we have definitely received a zero. If instead, it is less than 0.75 times the previous period, then we have definitely received the first half of a one. Only after such an event can we unambiguously decode the continuing bit stream. Once the bit stream is identified, ones and zeros will follow automatically, even if the data rate changes, provided the change is less than approximately one sixth of a bit period per bit.
Biphase requires two parameters: the name of the file to be encoded, and the file for the output data. It will produce a (quite large) audio file as described above. You will need to provide the complete filename; I recommend .wav as the suffix. The input file can be anything; text, executables, whatever. Note that the output file will cheerfully overwrite anything already existing with the same filename.
Most of the code is actually concerned with managing the creation of the audio file; creating the file, setting the header values, writing the header, and then after the data is written going back to the header to fill in the file sizes.
The biphase coding section is only three subroutine - and that for clarity... write_1() and write_0() make a fast cycle or a slow half-cycle respectively; 'last' holds the value written by a subroutine on its last half-cycle (and returned by the routine) so that the phase is maintained between calls. write_byte() packages the a given byte by wrapping it in a start and stop bit, and then calling write_1() or write_0() as appropriate to create the audio data. It also passes the 'last' value back. Everything is wrapped up in a simple routine that reads bytes from the input file and calls write_byte() for each byte.
Sequential bytes follow with no idle time other than the stop bit which is always a one. This gives the fastest possible transmission time for the selected bit rate. The bit rate itself can be modified quite a lot; provided there's at least one sample at each level between transition times it can be decoded - so with the audio package as described, it will work as fast as 8kb/s. Of course, if you are using a direct connection between processor, the signal can be arbitrarily fast, provided you have sufficient time to decode. On a 16MHz AVR I would expect (I haven't tried!) to be able to read at least half a megabit per second.
That's pretty much it... simple!
Unbiphase also expects two parameters: a mono 16-bit wave file - the bit rate is unimportant provided that the data is recorded at 1000 bits per second - and a filename to write the decoded output. It will overwrite any existing file of the same name.
The main part of the program looks after opening the audio file. As discussed above, this is not a standard way of writing RIFF files but it suffices for the purposes of this demonstration. The wave header information is read and sufficient checks are made to ensure that the file is of a suitable type. Because we have a defined bit rate, we can use the simpler of the methods discussed above to decode the signal - we have access to the sample rate in the wave header we can easily calculate how many samples mark the necessary periods.
Reading the data itself is broken into four layers of increasing complexity:
- At the lowest level, is_edge() decides whether the current sample represents a phase-change aka zero crossing
- get_period(), the next level up, repeatedly calls is_edge on each sample to return the period between phase changes
- get_bit() calls get_period once or twice as necessary to return a bit as either a one or a zero
- get_byte(), the highest level, unpacks the RS232-style packaging by waiting for the start byte and then shifting in the next eight bits. The stop bit provides somewhere for the next iteration of get_byte() to wait for the next start byte.
Decoding from a stored audio signal is simple since the timing information can be extracted effectively instantly; on something like the ACR the timing can be done several ways. If actual audio is being decoded, then the ADC complete interrupt can be used as a regular tick, or a logic-level signal can be used to drive an interrupt pin. An internal timer/counter is stored after every interrupt, and reset for the next period (the interrupt sense can be inverted after every interrupt).
The main routine simply calls get_byte() while there are still audio samples remaining.