The Kansas City Standard

I was pondering my laser transmitter the other day, and began to think of how I might transmit digital information from the Arduino to the remote receiver. Since I am old, I remember the old days where programs used to be stored on an obsolete audio storage medium called cassette tape. Indeed, the first storage device I ever used was the Atari 410 tape drive pictured on the right.

The Atari stored data at 600 baud, using FSK (data is stored as short bursts of two different tones, in the case of the Atari 3995 and 5327 hz), using a variant of the so-called Kansas City Standard. KCS was a standard for storing data on cassettes that was developed at a Byte magazine sponsored symposium in 1975.

Data is converted to short bursts of 1200Hz and 2400hz tones to represent zeroes and ones, respectively. Each burst is 1/300 of a second long, meaning that it sends 300 bits per second. Each 8 bit character is framed by a 0 start bit, and a pair of 1 stop bits so that character frames can be identified. It was designed to be a reliable if slow format, and succeeded on both counts. It transmits about 27 characters per second. An 8K download would take about five minutes.

It’s amazing we ever lived through the stone age.

Anyway, I thought it would be fun to make an Arduino library to send this information over my laser link, but first I decided that it would be good to test to make sure I understood how the format worked. So, I coded up a small program to generate some test .WAV files from an input data file. I made the problem simpler by generating the output at the somewhat non-standard sample rate of 9600 samples per second. This considerably simplifies the generation of samples, since they only would have amplitudes of zero, plus or minus one, and plus or minus sqrt(2)/2. I coded up the following C code, and generated this WAV file.

A WAV file that contains an ASCII test message, encoded in Kansas City Standard

The encoder is simple, the decoder, somewhat less so. So, to test that I was generating the proper bits, I used Martin Ward’s decoder written in Perl which did a good job of decoding the sample WAV files. I haven’t tested the robustness of this format with respect to noise yet, but it does appear to work reasonably well.

It wouldn’t be that hard to modify the simple sound generation code I used before to send data in this format. I think I will try to get to that next week.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sndfile.h>

/*
 * kc.c 
 * A program which takes a file as input, and encodes it via the old
 * Kansas City Standard - a 300 baud format that was used by old 
 * microcomputers to store data onto cassette tape.
 *
 * http://en.wikipedia.org/wiki/Kansas_City_standard
 * 
 * We are going to produce a 9600 sample per second output file...
 *
 * Each "baud" is 32 samples long.
 *
 * A '0' is 4 cycles @ 1200 Hz.
 * A '1' is 8 cycles @ 2400 Hz.
 *
 * 0 -  0 R2 1 R2 0 -R2 -1 -R2
 * 1 -  0 1 0 -1
 *
 */

#define R2      (.70710678118654752440f)

SNDFILE *sf ;
SF_INFO sfinfo ;

void
output(float f)
{
    sf_write_float(sf, &f, 1) ;
}

void
send(int bit)
{
    int c ;

    switch (bit) {
    case 0:
        for (c=0; c<4; c++) {
            output(0.f) ;
            output(R2) ;
            output(1.f) ;
            output(R2) ;
            output(0.f) ;
            output(-R2) ;
            output(-1.f) ;
            output(-R2) ;
        }
        break ;
    case 1:
        for (c=0; c<8; c++) {
            output(0.f) ;
            output(1.f) ;
            output(0.f) ;
            output(-1.f) ;
        }
        break ;
    default:
        abort() ;
    }
}


void
encode(int ch)
{
    int i ;
    send(0) ;           /* start bit... */
    for (i=0; i<8; i++) {
        send(ch & 1) ;
        ch = ch >> 1 ;
    }
    send(1) ;           /* two stop bits */
    send(1) ;
}

main()
{
    int i, ch ;

    sfinfo.channels = 1 ;
    sfinfo.samplerate = 9600 ;
    sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16 ;

    sf = sf_open("test.wav", SFM_WRITE, &sfinfo) ;

    for (i=0; i<9600/4; i++)
        output(0.) ;
    while ((ch = getchar()) != EOF)
        encode(ch) ;
    for (i=0; i<9600/4; i++)
        output(0.) ;

    sf_close(sf) ;
}