This is the third post in a multi-part series about viewdata. A few members of the hackspace are working on a project to connect a ZX Spectrum with Prism VTX 5000 modem to an emulation of a viewdata system, providing a live demonstration experience.
A word in advance – this post is quite theory-heavy, and contains quite a bit of technical information. However, hopefully it’s of interest to people. You can find the complete source code for my softmodem at https://github.com/aquila12/v23.
Modems for viewdata systems use Mode 2 of the ITU V.23 standard for communication. This specifies the signal format for encoding the data as transmitted across a phone line. Mode 2 allows a maximum of 1200 baud operation, and specifies the mark (logic ‘1’) and space (logic ‘0’) frequencies as 1300Hz and 2100Hz, respectively. The standard also specifies a backward channel running up to 75 baud, using 390Hz and 450Hz for mark and space, respectively.
Viewdata uses the faster forward channel to deliver data to the customer’s terminal, while key presses are transmitted immediately over the backward channel. With the standard 7-bit ASCII data, even parity bit and the stop and start bits, the frames are a total of 10 bits long – so the forward channel can relay 120 characters per second, with the backward channel running at the much slower 7.5 characters per second. Viewdata frames are up to 40 characters wide, with 24 lines. This divides out neatly so that three complete lines of of non-escaped data can be sent each second (escape codes are used for display attribute changes), with a full frame taking around 8 seconds to transmit.
While a regular V.23 compatible modem could be used, these are somewhat rare now. To communicate with the Prism, we decided to use a software modem. While we’d had some success with minimodem, I found it slightly problematic to use in practice. On my laptop, it used a lot of CPU to demodulate signals, and it struggled to synchronise with the incoming data. I decided to put my somewhat rusty signal processing theory to practice and wrote a V.23-only softmodem to use for this project. My aim was to avoid performing any fourier transforms (e.g. FFTs) on the signal, as these are computationally expensive, and instead stick to simple operations such as moving-average filters and differentiators, as these can be made very computationally efficient.
My first arrangement has limited success, but was too sensitive to noise. The arrangement, briefly, did amplitude demodulation on the mark and space frequencies, then compared the magnitudes to see which was stronger, and fed the output to a shift register which detected the serial frames. Once I’d thought a bit more about the problem, I decided to attempt demodulation using an approach more suited to the frequency-shift-keyed modem signal, which produced much better results. I’ll describe that in more detail here.
Taking an input signal, I multiply on a complex local oscillator which runs halfway between the mark and space frequencies. This essentially moves the frequency spectrum down to DC – anything lower than the oscillator is seen as a “negative” frequency, while anything above is seen as a “positive” frequency – see the image below for a brief illustration. For a complex (I-Q) oscillator, this means that the direction of rotation of the output vector tells you whether the dominant frequency at the input is above or below the local oscillator frequency.
It’s worth mentioning that multiplying on the local oscillator doesn’t just “move” the spectrum – it causes two copies. One is shifted down around DC (the “difference” spectrum), while the other is shifted upwards (the “sum” spectrum). Because of this, and also because the input isn’t completely clean (in fact, it might contain other signals), the signal must be filtered before determining the direction the vector is rotating in. I used a moving-average filter for this.
Moving-average filters are very simple – essentially the samples between two points in time are summed. This is easy to implement efficiently as you only need to keep track of the samples within that period, and keep a running total. As each sample is added to the total, the sample added least recently is subtracted and removed from the buffer. These filters have a very characteristic transfer function (i.e. what frequencies they reject). The moving-average filter completely rejects signals at regular frequencies, and those frequencies occur depending on the period the filter is averaging over – the illustration shows the shape of the response. I used this fact to specifically tune the demodulation filters. When demodulating the forward channel, I placed the first “null” to eliminate the backward channel signal as much as possible (as in the picture), as the backward channel is within the side-bands of the forward channel. For demodulating the backward channel, since the band is quite narrow, I just placed the first null so it was away from the frequencies of interest.
The filtered signal is then passed to a function which determines the current phase of the vector. This essentially divides the vector components to produce the tangent of the phase angle, and then passes that through a function to determine the angle. As the precise angle isn’t important for this, I used a simple approximation for this – the tangent function, between -45° and +45°, is nearly a straight line, and it’s straightforward to replicate this approximation through the entire circle by flipping the division upside down to stay in this quadrant.
Once the phase angle is determined, we just need to average its change over each bit, and use the sign of this (positive or negative) to correctly demodulate the signal. The simplest way to write this was to take the derivative of the phase, and then run this through a moving-average filter. The output is then fed to the same frame-detection routine as I’d drafted for my first attempt, which I’ve illustrated below.
I’m aware this topic has been quite in-depth as far as the implementation goes, but I’m hoping it makes some sense to those of you who perhaps dabble in filters or radio-related topics! If you’d like me to expand on anything I’ve discussed here, please get in touch and I’ll do my best to address your query.
Next time, I’ll talk a bit about the viewdata frame format and how we’re writing our frames.