Home .. Personal .. ARTICLES .. Software .. MUD .. Links .. Search

SUPPORTING 1200/75 BAUD
by Eddy Carroll, June 1988

Note: At the time this article was written, most modems in use in the UK were so-called dumb modems that could only receive at 1200 baud and transmit at 75 baud. These were generally considered incompatible with PC and Amiga serial ports, which used the same baud rate for both sending and receiving.

The method presented below will allow you to simulate a serial port which can send at 75 baud and receive at 1200 baud. 1200/75 is very popular in the UK, and is usually the only speed available for viewdata services.
   Normally, on a computer which uses the same baud rate for both sending and receiving, you must use an "intelligent" modem which can communicate with your computer at 1200/1200 and internally convert this to a speed of 1200/75. However, using the following method, you can use so-called "dumb" modems which contain no microprocessor, and can only connect to the computer at 1200/75. The technique should work for any computer which can send and receive at 1200 baud.
   The basic idea is that it takes the same amount of time to transmit 16 characters at 1200 baud as it does to transmit a single character at 75 baud. If these 16 characters are correctly chosen, then the resulting signal sent down the serial line will be a very close approximation of the signal that would be generated by the 75 baud character.
   All characters being sent or received follow the same format: A start bit (value 0), eight data bits starting with the least significant bit and ending with the most significant bit, and finally a stop bit (value 1). This makes ten bits altogether. At 75 baud, one of these bits will correspond to 16 bits at 1200 baud. Unfortunately, since each character uses ten bits, there is no easy correspondance between these 16 bits and the single 75 baud bit, as the 16 bits will be formed from 1.6 characters.
   There is another problem in that you, the programmer, have only got control over eight of the ten bits in each character being sent. The other two bits (the start and stop bits) will be set to 0 and 1 respectively. However, with a little careful bit-twiddling, any negative effects caused by these can be removed.

Translating a single character
Let's see how to go about translating a single character at 75 baud into 16 characters at 1200 baud. The character chosen is 0x55, since each bit in this character is different from its two partners, and this will make it easier to devise an algorithm. In the following example, 'S' is a start bit and 'E' is a stop (end) bit. '0' and '1' are used to represent actual data bits which can be modified by the programmer. Remember, 'S' is transmitted as '0' and 'E' is transmitted as '1':

      75 baud character = S10101010E

The ideal representation of this at 1200 baud is:

      0000000000000000 1111111111111111
      0000000000000000 1111111111111111
      0000000000000000 1111111111111111
      0000000000000000 1111111111111111
      0000000000000000 1111111111111111

But the nearest approximation that can be obtained, taking into account start and stop bits is:

      S00000000ES00000 111ES11111111ES1
      0000000ES0000000 1ES11111111ES111
      00000ES00000000E S11111111ES11111
      000ES00000000ES0 1111111ES1111111
      0ES00000000ES000 11111ES11111111E

Extracting the data part of each character (the bits between each S and E) gives the bytes which must be sent at 1200 baud to reproduce the original 75 baud character. The 16 bytes are:

            +0       +1       +2       +3
      0: 00000000 00000111 11111111 10000000
      4: 00000001 11111111 11100000 00000000
      8: 11111111 11111000 00000000 01111111
     12: 11111110 00000000 00011111 11111111

From this, it is apparent that bytes 0, 2, 5, 7, 8, 10, 13, 15 are all equal to the value of one the of bits in the original byte. The bits in the original byte are laid out as follows:

         Pos: 0123456789
       Value: S10101010E

In fact, the correlation is as follows:

    Byte Num: 0   2   5   7   8  10  13  15
     Bit Pos: 0   1   3   4   5   6   8   9

This leaves the bytes 1, 3, 4, 5, 9, 11, 12, 14 to be filled in. The values for these bytes are a little more complicated. The calculation can be simplified by noting that the conversion procedure for the second four bytes is identical to that for the first four bytes:

     Byte 1 : [7..3] = bit 0, [2..0] = bit 1
     Byte 3 : [7..7] = bit 1, [6..0] = bit 2
     Byte 4 : [7..1] = bit 2, [0..0] = bit 3
     Byte 6 : [7..5] = bit 3, [4..0] = bit 4

     Byte 9 : [7..3] = bit 5, [2..0] = bit 6
     Byte 11: [7..7] = bit 6, [6..0] = bit 7
     Byte 12: [7..1] = bit 7, [0..0] = bit 8
     Byte 14: [7..5] = bit 8, [4..0] = bit 9

However, although this method will give a good approximation of the 75 baud character, it is not perfect. It is possible for a solitary stop bit to occur between two '0' bits for example, which will effectively cause a spike in the signal. Depending on the position of this spike relative to the start of the current 75 baud 'bit' being sent, this may or may not confuse the receiving modem.
   This effect can be reduced by "smoothing" the resultant signal manually after creating the 16 character array. Possible problem bytes are ones such as 0x80, 0x7F, 0xFE, 0x01, all of which can result in spikes as described. The signal is smoothed by removing the spike, and this can be achieved by altering the values to 0x00 or 0xFF. Although it seems as if this makes the signal a less accurate approximation, the overall signal is smoother and provides much more reliable operation.
   So, to send a byte at 75 baud, it must first be converted to an array of 16 bytes which are then sent in one go at 1200 baud. It is extremely important that these bytes are sent continuously at 1200 baud, as a single block, otherwise errors will probably creep in.
   The conversion obviously needs to be performed efficiently, since it will be taking place in real time as characters are transmitted. The method adapted by the code below is to build a lookup table as part of the program initialisation, and then to use this table for the actual conversion. A table for all 256 bytes would require 16 bytes for each entry, a total of 4Kb. While this would be the fastest solution, it is not particularly memory efficient, and generating the table takes a reasonable amount of time. A less greedy method is used in the example code -- this utilises the fact that a group of five 75 baud bits corresponds directly to eight 1200 baud characters.
   Thus, a ten bit 75 baud character can be split into two halves, each containing five bits. These two halves are converted seperately, and the two results concatenated together to produce the final 16 byte image. This method requires a lookup table of only 32 entries, each of eight characters, so the total memory requirement is 256 bytes. If memory is at a premium, the initialisation routine below could be replaced by simply defining the lookup table as constant data at compile time -- this would remove the overhead of initialising the table, and would also reduce the code size of the final program.

Sample code
Two routines are given below. The first, BuildConv75to1200() initialises the lookup table, and the second, Conv75to1200() converts a single character into 16 bytes, stored in a passed array of bytes. This latter is provided both as a function for memory economy, and a macro for speed. The macro form is the preferred method.
   Note that a few shortcuts have been taken to increase efficiency of the code. In particular, the eight bytes in the lookup table are stored as two longwords, since this allows them to be copied more efficiently. Likewise, the array used to store the converted character should be an array of four longwords rather than 16 characters.

/*
 *    Standard types used throughout
 */
typedef ULONG unsigned long int;  /* 32 bit type */
typedef UWORD unsigned short int; /* 16 bit type */

/*
 *    BuildConv75to1200()
 *
 *    Sample source code to convert a single 8-bit
 *    character into a 16-byte array, so that
 *    sending the contents of the array at 1200
 *    baud will have a similar effect to sending
 *    the original character at 75 baud.
 *
 *    Some smoothing of the output is done to
 *    prevent confusing the host - otherwise, some
 *    combinations of 0101 can occur which lead to
 *    invalid characters.
 *
 *    A translation table is built, to speed up
 *    the conversion.  This table uses the fact
 *    that the conversion procedure for the first
 *    five bits is identical to that for the
 *    remaining five bits, so only 32 eight byte
 *    sets need be stored.
 * 
 *    The Conv75to1200() routine uses the lookup
 *    tables, therefore you must call this
 *    BuildConv75To1200() before making calls to
 *    Conv75to1200().
 */

ULONG ConvLow[32], ConvHigh[32];

UWORD Power[] = { 0x01,  0x02,  0x04,  0x08,
                  0x10,  0x20,  0x40,  0x80,
                  0x100, 0x200 };

UWORD Left[]  = { 0,     0xF8,  0,     0x80,
                  0xFE,  0,     0xE0,  0,
                  0,     0xF8,  0,     0x80,
                  0xFE,  0,     0xE0,  0 };

UWORD Right[] = { 0,     0x07,  0,     0x7F,
                  0x01,  0,     0x1F,  0,
                  0,     0x07,  0,     0x7F,
                  0x01,  0,     0x1F,  0 };

#define EasyBit(pos)   \
    ((outbyte & Power[pos]) ? 0xFF : 0x00 )

#define HardBit(x,pos) \
    ( ((outbyte & Power[pos]) ? Left[x] : 0x00 ) | \
      ((outbyte & Power[pos+1]) ? Right[x] : 0x00) )

void BuildConv75to1200()
{
  unsigned char out[16];
  UWORD outbyte;
  ULONG *longout;
  int i;

  longout = (ULONG *)out;

  for (i = 0; i < 32; i += 2) {
    /*
     *    Generate 10-bit byte
     */
    outbyte = i | ((i+1) << 5);

    out[0]  = EasyBit(0);
    out[2]  = EasyBit(1);
    out[5]  = EasyBit(3);
    out[7]  = EasyBit(4);
    out[8]  = EasyBit(5);
    out[10] = EasyBit(6);
    out[13] = EasyBit(8);
    out[15] = EasyBit(9);

    out[1]  = HardBit( 1, 0);
    out[3]  = HardBit( 3, 1);
    out[4]  = HardBit( 4, 2);
    out[6]  = HardBit( 6, 3);
    out[9]  = HardBit( 9, 5);
    out[11] = HardBit(11, 6);
    out[12] = HardBit(12, 7);
    out[14] = HardBit(14, 8);

    /*
     *    Now smooth out any spikes in the signal
     */
    if (out[3]  == 0x80) out[3]  = 0x00;
    if (out[3]  == 0x7f) out[3]  = 0xff;
    if (out[11] == 0x80) out[11] = 0x00;
    if (out[11] == 0x7f) out[11] = 0xff;
    if (out[4]  == 0xFE) out[4]  = 0xFF;
    if (out[4]  == 0x01) out[4]  = 0x00;
    if (out[12] == 0xFE) out[12] = 0xFF;
    if (out[12] == 0x01) out[12] = 0x00;

    /*
     *    Store eight bytes equivalent for
     *    each pair of five bits
     */
    ConvLow[i]    = longout[0];
    ConvHigh[i]   = longout[1];
    ConvLow[i+1]  = longout[2];
    ConvHigh[i+1] = longout[3];
  }
}


/*
 *    Conv75to1200()
 *
 *    Converts character ch to be sent at 75 baud
 *    into 16 bytes which can be sent at 1200 baud
 *    for the same effect. Out is a pointer to an
 *    array of four longwords where the output data
 *    is stored. The lookup table generated in
 *    BuildConv75to1200() is used for speed.
 *
 */
void Conv75to1200(unsigned char ch, ULONG out[])
{
  UWORD conv;
  int   lownibble;
  int   hinibble;

  /*
   *    Add start & stop bits to ch
   */
  conv = (UWORD)ch + (UWORD)ch + 0x200;
  lownibble = (conv & 0x1F);
  hinibble  = (conv >> 5);

  /*
   *    Now perform the conversion using the table
   */
  out[0] = ConvLow[lownibble];
  out[1] = ConvHigh[lownibble];
  out[2] = ConvLow[hinibble];
  out[3] = ConvHigh[hinibble];
}


/*
 *    Conv75to1200()
 *
 *    This is a macro version of the above
 *    function. 'conv', 'ln' and 'hn' must be
 *    previously defined as UWORD, preferably
 *    as register vars. 'Out' is a pointer to
 *    an array of 4 ULONGs and ch is a character.
 *    (Yes, it's a bit of a kludge; sorry!)
 */

#define Conv75to1200(ch,out)     \
    { conv   = ch + ch + 0x200;  \
      ln     = conv & 0x1F;      \
      hn     = conv >> 5;        \
      out[0] = ConvLow[ln];      \
      out[1] = ConvHigh[ln];     \
      out[2] = ConvLow[hn];      \
      out[3] = ConvHigh[hn];     \
    }

/*
 *    End of sample conversion code
 */

Home .. Personal .. ARTICLES .. Software .. MUD .. Links .. Search

Last updated 26 November 2000. Comments to ecarroll@iol.ie.