Protocols & Software on Transmission Modes : Baudot, Amtor, PSK31, Pactor, GTOR etc.

This post is about some of the common transmission modes such as Baudot, Amtor, PSK31, Pactor etc. which are used in amateur radio systems.

My basic aim is to show some easy first steps which leads developing non-commercial softwares for the protocols structures. Detailed explanations over the protocols are mostly taken from Arrl’s Hf Digital Handbook.

I had written a couple of frameworks and some interfaces for the amateur and commercial radio systems in the past years. Most of the researches i had made were on demodulator and decoders. Thats why i pay more attention on destruction instead of construction. You know what i mean : ).

This snapshot is one of my old works which is designed for realtime decoders over some specific protocols. Actually those protocols can be demodulated and decoded in an offline environment but also can be decoded in realtime by using a good radio-antenna setup with a sharp digitizer.

Demodulation methods for universal FSK, PSK and QAM (4-256) are also included in the framework. Phase and time recovery techniques are used for the correction of corrupted signals. For the equalization of the high order QAM signals RC + optimal step size algorithm (self-adapting) is used.

OFDM demodulation and decoding techniques will be available soon here. Not included in this scope.

A protocol-decoder software design bby CRCS

Realtime & Offline Decoders

Now lets start with legendary RTTY.

Baudot Radioteletype (RTTY)

One of the first data communications codes to receive widespread use had five bits (also called levels”) to present the alphabet, numerals, symbols and machine functions. In the US, we use international Telegraph Alphabet No. 2 (ITA2), commonly called Baudot, as specified in FCC §97.309(a)(1). In the United Kingdom, the almost-identical code is called Murray code.

There are many variations in five-bit coded character sets, principally to accommodate foreign-language alphabets. Five-bit codes can directly encode only 25 = 32 different symbols. This is insufficient to encode 26 letters, 10 numerals and punctuation. This problem can be solved by using one or more of the codes to select from multiple code-translation tables. ITA2 uses a LTRS code to select a table of upper-case letters and a FIGS code to select a table of numbers, punctuation and special symbols.

Certain symbols, such as carriage return, occur in both tables. Unassigned ITA2 FIGS codes may be used for the remote control of receiving printers and other functions.

FCC rules provide that ITA2 transmissions must be sent using start-stop pulses, as illustrated in Fig 1.1. The bits in the figure are arranged as they would appear on an oscilloscope.

Speeds and Signaling Rates

The signaling speeds for RTTY are those used by the old TTYs, primarily 60 WPM or 45.45 bauds. The baud (Bd) is a unit of signaling speed equal to one pulse (event) per second. The signaling rate, in bauds, is the reciprocal of the shortest pulse length.

Transmitter Keying

When TTYs and TUs (terminal units) roamed the airwaves, frequency-shift keying (FSK) was the order of the day. DC signals from the TU controlled some form of reactance (usually a capacitor or varactor) in a transmitter oscillator stage that shifted the transmitter frequency. Such direct FSK is still an option with some new radios.

AFSK

Multimode communications processors (MCPs), however, generally connect to the radio AF input and output, often through the speaker and microphone connectors, and sometimes through auxiliary connectors.

They simply feed AF tones to the microphone input of an SSB transmitter or transceiver. This is called AFSK for “audio frequency-shift keying. ”When using AFSK, make certain that audio distortion, carrier and unwanted sidebands do not cause interference. Particularly when using the low tones discussed later, the harmonic distortion of the tones should be kept to a few percent. Most modern AFSK generators are of the continuous-phase (CPFSK) type. Also remember that equipment is operating at a 100% duty cycle for the duration of a transmission. For safe operation, it is often necessary to reduce the transmitter power output (25 to 50% of normal) from the level that is safe for CW operation.

What are High and Low Tones?

US amateurs customarily use the same modems (2125 Hz mark, 2295 Hz space) for both VHF AFSK and HF via an SSB transmitter. Because of past problems (when 850-Hz shift was used), some amateurs use “low tones” (1275 Hz mark, 1445 Hz space). Both high and low tones can be used interchangeably on the HF bands because only the amount of shift is important. The frequency difference is unnoticed on the air because each operator tunes for best results.

On VHF AFSK, however, the high and low tone pairs are not compatible.

Transmit Frequency

It is normal to use the lower sideband mode for RTTY on SSB radio equipment.

In order to tune to an exact RTTY frequency, remember that most SSB radio equipment displays the frequency of its (suppressed) carrier, not the frequency of the mark signal. Review your MCP’s manual to determine the tones used and calculate an appropriate display frequency. For example, to operate on 14,083 kHz with a 2125-Hz AFSK mark frequency, the SSB radio display (suppressed-carrier) frequency should be 14,083 kHz + 2.125 kHz = 14,085.125 kHz.

Receiving Baudot

TUs (Terminal Units) have been replaced by multi-mode communications processors (MCPs), which accept AF signals from a radio and translate them into common ASCII text or graphics file formats (see Fig 9.11). Because the basic interface is via ASCII, MCPs are compatible with  virtually any PC running a simple terminal program. Many MCPs handle CW, RTTY, ASCII, packet, fax, SSTV and new digital modes as they come into amateur use. To an increasing extent, personal computer sound cards with appropriate software are a viable and low-cost alternative to MCPs. However, sound cards have their limitations and dedicated hardware can more  efficiently perform some operations.

AFSK Demodulators

An AFSK demodulator takes the shifting tones from the audio output of a receiver and produces TTY keying pulses. FM is a common AFSK demodulation method. The signal is first bandpass filtered to remove out-of-band interference and noise. It is then limited to remove amplitude variations. The signal is demodulated in a discriminator or a PLL. The detector output is low pass filtered to remove noise at frequencies above the keying rate. The result is fed to a circuit that determines whether it is a mark or a space. AM (limiterless) detectors, when properly designed, permit continuous copy even when the mark or space frequency fades out completely. At 170-Hz shift, however, the mark and space frequencies tend to fade at the same time. For this reason, FM and AM demodulators are comparable at 170-Hz shift.

Diversity Reception

Although not restricted to RTTY, diversity reception can be achieved by using two antennas, two receivers and a dual demodulator. Some amateurs are using it with good results. One of the antennas would be the normal station antenna for that band. The second antenna could be either another antenna of the same polarization located at least 3/8-wavelength away, or an antenna of the opposite polarization located near the first antenna. A problem is to get both receivers on the same frequency without carefully tuning each one manually. Two demodulators are needed for this type of diversity.

Also, some type of diversity combiner, selector or processor is needed. Many commercial or military RTTY demodulators are equipped for diversity reception.

The payoff for using diversity is a worthwhile improvement in copy. Depending on fading conditions, adding diversity may be equivalent to raising transmitter power severalfold.Communication_1 Communication_2

Examine the linPsk library then you can find out how the protocol actually works. As an example start with the following demodulator class file written in C++.

fftw is one of the best libraries which is written for fast fourier transformation. firfilter.h includes the classes for bandpass lowpass and highpass filtering which are needed to pull the communication signals to the baseband. You can rewrite all code and libraries in your way even with a shorter code. Just an hint for fftw library, do not try to make it better or shorter its already very efficient.

 

/***************************************************************************
                        rttydemodulator.cpp  -  description
                             -------------------
    begin                : Mon Jun 4 2001
    copyright            : (C) 2001 by Volker Schroer
    email                : dl1ksv@gmx.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "rttydemodulator.h"
#include "firfilter.h"
#include "constants.h"

RTTYDemodulator::RTTYDemodulator() : CDemodulator()
{
  int i;
  ShiftOn = false;
  BufferPointer = 0;
  BufferCount = 0;
  StopBitLength = 0;
  RxFrequency = 0.0; // Will be set correctly in setFrequency !
//Initialize ExtraParameter
  extraParameter.stopbits = Onepoint5;
  extraParameter.parity = None;
  extraParameter.reverse = true;
  extraParameter.offset = 170;
  /** Have to use new with ggc 4, otherwise I get a lot of nan's in fftw_execute, at least on AMD64  **/
  pIn        = new complex<double>[FFTLENGTH];
  pOutF0     = new complex<double>[FFTLENGTH];
  pOutF1     = new complex<double>[FFTLENGTH];
  pFilterF0  = new complex<double>[FFTLENGTH];
  pFilterF1  = new complex<double>[FFTLENGTH];
  pFilterIn  = new complex<double>[FFTLENGTH];
  pResultF0  = new complex<double>[FFTLENGTH];
  pResultF1  = new complex<double>[FFTLENGTH];
  pOverlapF0 = new complex<double>[FFTFILTERLENGTH-1];
  pOverlapF1 =  new complex<double>[FFTFILTERLENGTH-1];

  pforward   = fftw_plan_dft_1d ( FFTLENGTH, ( fftw_complex * ) pIn, ( fftw_complex * ) pOutF0, FFTW_FORWARD, FFTW_ESTIMATE );
  pbackwardF0 = fftw_plan_dft_1d ( FFTLENGTH, ( fftw_complex * ) pOutF0, ( fftw_complex * ) pResultF0, FFTW_BACKWARD, FFTW_ESTIMATE );
  pbackwardF1 = fftw_plan_dft_1d ( FFTLENGTH, ( fftw_complex * ) pOutF1, ( fftw_complex * ) pResultF1, FFTW_BACKWARD, FFTW_ESTIMATE );
  pfilter  = fftw_plan_dft_1d ( FFTLENGTH, ( fftw_complex * ) pFilterIn, ( fftw_complex * ) pFilterF0, FFTW_FORWARD, FFTW_ESTIMATE );

  for ( i = 0; i < FFTFILTERLENGTH - 1;i++ )
  {
    pOverlapF0[i] = complex<double> ( 0., 0. );
    pOverlapF1[i] = complex<double> ( 0., 0. );
  }
  filteredSamples = 0;
  delayZ = complex<double> ( 0., 0. );
}

RTTYDemodulator::~RTTYDemodulator()
{
  fftw_destroy_plan ( pforward );
  fftw_destroy_plan ( pbackwardF0 );
  fftw_destroy_plan ( pbackwardF1 );
  fftw_destroy_plan ( pfilter );
  fftw_free ( pIn );
  fftw_free ( pOutF0 );
  fftw_free ( pOutF1 );
  fftw_free ( pFilterF0 );
  fftw_free ( pFilterF1 );
  fftw_free ( pFilterIn );
  fftw_free ( pResultF0 );
  fftw_free ( pResultF1 );
  fftw_free ( pOverlapF0 );
  fftw_free ( pOverlapF1 );
}
/** returns the asci char corresponding to the baudot code */
char RTTYDemodulator::baudot_code ( char data )
{
  /** Table of letters */

  static const char letters[32] =
  {
    0x00, 'E', '\r', 'A', ' ', 'S', 'I', 'U',
    '\n', 'D', 'R', 'J', 'N', 'F', 'C', 'K',
    'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q',
    'O', 'B', 'G', '^', 'M', 'X', 'V', '^'
  };

  /** Table of symbols */
  static const char symbols[32] =
  {
    0x00, '3', '\r', '-', ' ', '\'', '8', '7',
    '\n', '$', '4', '#', ',', '!', ':', '(',
    '5', '"', ')', '2', '#', '6', '0', '1',
    '9', '?', '&', '^', '.', '/', '=', '^'
  };

  char c;

  switch ( data )
  {
    case 0x1f :
      ShiftOn = false;  //LTRS
      c = 0;
      break;
    case 0x1b :
      ShiftOn = true;   //FIGS
      c = 0;
      break;
    default:
      if ( !ShiftOn )
        c = letters[ ( int ) data];
      else
        c = symbols[ ( int ) data];
      break;
  }

  if ( c == ' ' ) // Unshift on Space
    ShiftOn = false;

  return c;
}

bool RTTYDemodulator::Init ( double FS, int  )
{
  SampleRate = FS;
  Baudrate = 45.45;
  NumberOfBits = 5;
  SymbolLength = int ( FS / Baudrate + 0.5 );
  Status = WaitingForMark;
  FrequencyChanged = false;
  ave1 = 0.5;
  ave2 = 0.0;
  setRxFrequency ( 1000. );
  F0inc = 0.;
  F1inc = 0.;
  F0max = 0.;
  F1max = 0.;
  return true;
}

void RTTYDemodulator::ProcessInput ( double *input, double * )
{
  char c1;
  int i, j, count;
  int StartBitCount, StartBitLength, StartBit2Lead;
  int StopBit2Count;
  int actSample;
// int Filtered[RESULTLENGTH];
// complex<double> omegaF0[RESULTLENGTH],omegaF1[RESULTLENGTH];
  int Filtered[RESULTLENGTHDOWN];
  double omegaF0[RESULTLENGTHDOWN], omegaF1[RESULTLENGTHDOWN];
  double f0sum, f1sum;
  double zf0sum, zf1sum, StartBitValue;

  count = 0;

// Calculating StopbitlengthStopBitLength
  switch ( extraParameter.stopbits )
  {
    case One:
      StopBitLength = NUMBEROFPROBES;
      break;
    case Onepoint5:
      StopBitLength = ( 3 * NUMBEROFPROBES ) / 2;
      break;
    case Two:
      StopBitLength = 2 * NUMBEROFPROBES;
      break;
  }

  while ( ( count + RESULTLENGTH ) < BUF_SIZE )
  {
    for ( i = filteredSamples;i < RESULTLENGTH;i++ )
      pIn[i] = complex<double> ( input[count++], 0. );
    filteredSamples = 0;
    fftw_execute ( pforward );
    execFilter();
    downmix ( pResultF0, pResultF1, RESULTLENGTH, Filtered, omegaF0, omegaF1 );
    actSample = 0;
    while ( actSample < RESULTLENGTHDOWN )
    {
      if ( ( BufferCount < SAMPLEBUFFERLENGTH ) )
      {
        while ( ( BufferCount < SAMPLEBUFFERLENGTH ) && ( actSample < RESULTLENGTHDOWN ) )
        {
          BufferCount++;
          Demod[BufferPointer] = Filtered[actSample];
          SF0[BufferPointer] = omegaF0[actSample];
          SF1[BufferPointer] = omegaF1[actSample];
          if ( !extraParameter.reverse )
          {
            Demod[BufferPointer] = -Demod[BufferPointer];
            SF0[BufferPointer] = omegaF1[actSample];
            SF1[BufferPointer] = omegaF0[actSample];
          }
          BufferPointer++;
          BufferPointer = BufferPointer % SAMPLEBUFFERLENGTH;
          actSample ++;
        } // End of filling CharacterData
      }
      while ( BufferCount == SAMPLEBUFFERLENGTH )
      {
        switch ( Status )  // Now let's analyze the data
        {
          case WaitingForMark:        // Waiting for Stopbit, previous state undefined
            // Check, if we are possibly at the beginning of a stop bit
            i = 0;
            while ( ( i < BufferCount ) && ( Demod[ ( BufferPointer + i ) % SAMPLEBUFFERLENGTH] < 0 ) )
              i++;
            if ( i == 0 ) // At the beginning
            {
              StopBit1Count = 0;
              f0sum = 0.;
              f1sum = 0.;
              StartBitLead = 0;
              while ( i < StopBitLength )
              {

                f0sum += SF0[ ( BufferPointer + i ) % SAMPLEBUFFERLENGTH];
                f1sum += SF1[ ( BufferPointer + i ) % SAMPLEBUFFERLENGTH];
                if ( Demod[ ( BufferPointer + i++ ) % SAMPLEBUFFERLENGTH] > 0 )
                {
                  StopBit1Count++;
                  StartBitLead = 0;
                }
                else
                  StartBitLead++;
              }
              zf0sum = abs ( f0sum );
              zf1sum = abs ( f1sum );
              StopBit1Value = zf1sum - zf0sum;
              if ( StartBitLead > 2 )
                StartBitLead = 2;
              if ( ( StopBit1Count  > StopBitLength / 2 ) || ( StopBit1Value > 0 ) )
                Status = WaitingForSpace;
              else
                BufferCount -= ( StopBitLength - StopBit1Count );
            }
            else         // Refill the buffer
              BufferCount -= i;
            break;

          case WaitingForSpace:      // Stopbit seems to be found, now waiting for transition
            //      i = StopBitLength  ;
            i = StopBitLength - StartBitLead;
            while ( ( i < SAMPLEBUFFERLENGTH ) && ( Demod[ ( BufferPointer + i ) % SAMPLEBUFFERLENGTH ] > 0 ) )
              i++;
            if ( i == SAMPLEBUFFERLENGTH )
            {
              BufferCount = StopBitLength;  // No Space found, keep only StopBitLength Samples
              CalcQuality((float)0.);
            }
            else
            {
              Status = CheckingStartBit;
              //         i = i - StopBitLength;
              //         i = i - StopBit1Count;
              i = i - StopBitLength + StartBitLead;
              if ( ( StartBitLead == 0 ) && ( i > 2 ) )
                i--;
              BufferCount -= i;             // Refill buffer
            }
            break;

          case CheckingStartBit:
            //      j = StopBitLength + BufferPointer;
            j = StopBitLength - StartBitLead + BufferPointer;
            StartBitCount = 0;
            StartBitLength = NUMBEROFPROBES;
            f0sum = 0.;
            f1sum = 0.;
            for ( i = 0; i < NUMBEROFPROBES; i++ )
            {
              f0sum += SF0[ ( i + j ) % SAMPLEBUFFERLENGTH];
              f1sum += SF1[ ( i + j ) % SAMPLEBUFFERLENGTH];
              if ( Demod[ ( i + j ) % SAMPLEBUFFERLENGTH]  < 0 )
                StartBitCount++;
            }
            zf0sum = abs ( f0sum );
            zf1sum = abs ( f1sum );
            StartBitValue = zf1sum - zf0sum;
            CalcQuality(abs(StartBitValue)/(zf0sum+zf1sum));
            Status = CheckingStopBits;
            /**
            if ( (StartBitCount > NUMBEROFPROBES/2) || ( zf0sum > zf1sum))
             Status = CheckingStopBits;
             else
             {
              Status = WaitingForMark;           // Was'nt the correct start bit
              BufferCount -= StopBitLength;
             }
             **/
            break;

          case CollectingByte:
            c1 = 0;
            for ( i = 0;i < NumberOfBits; i++ )
            {
              int j1, BitCount;
              j = ( BufferPointer + StopBitLength - StartBitLead + StartBitLength + i * NUMBEROFPROBES ) % SAMPLEBUFFERLENGTH;
              //       j=(BufferPointer + StopBit1Count + StartBitLength + i * NUMBEROFPROBES ) % SAMPLEBUFFERLENGTH;
              BitCount = 0;
              f0sum = 0.;
              f1sum = 0.;
              for ( j1 = j;j1 < j + NUMBEROFPROBES; j1++ )
              {

                if ( Demod[j1 % SAMPLEBUFFERLENGTH] > 0 )
                  BitCount++;
                f1sum += SF1[ j1 % SAMPLEBUFFERLENGTH];
                f0sum += SF0[ j1 % SAMPLEBUFFERLENGTH];

              }
              zf0sum = abs ( f0sum );
              zf1sum = abs ( f1sum );
              CalcQuality(abs(zf0sum-zf1sum)/(zf0sum+zf1sum));
              //       if ( BitCount > NUMBEROFPROBES/2 )
              //       if ( (zf1sum > zf0sum) || ( BitCount > NUMBEROFPROBES/2 ) )
              if ( zf1sum > zf0sum )
                c1 |= ( 1 << i );
            }
            if ( ( c1 > 0 ) && ( !Squelch || ( Squelch && ( ( unsigned int ) ( 100.*ave1 ) > CDemodulator::Threshold ) ) ) )
            {
              c1 = baudot_code ( c1 );
              if ( c1 > 0 )          // FIGS or LTRS result in c1 = 0 !
                emit newSymbol ( c1 );
            }

            if ( extraParameter.parity != None )
              Status = CheckingParity;
            else
            {
              StopBit1Value = zf1sum - zf0sum;
              /**
              if (  (StopBit2Count > (StopBitLength*2)/3)
                    ||(StopBit1Count > (StopBitLength*2)/3)
                    ||(StartBitCount > (StartBitLength*2)/3)   )
               **/
              if ( ( StopBit2Count > ( StopBitLength*2 ) / 3 ) || ( StopBit1Value > 0 ) )
                Status = WaitingForSpace;
              else
                //        Status=WaitingForMark;
                Status = CheckingStartBit;
              BufferCount -= ( StopBitLength + StartBitLength - StartBitLead  + 5 * NUMBEROFPROBES );
              StartBitLead = StartBit2Lead;
              StopBit1Count = StopBit2Count;

            }
            break;

          case CheckingParity: // Here we need BitsInData
            break;

          case CheckingStopBits:
            f0sum = 0.;
            f1sum = 0.;

            StopBit2Count = 0;
            StartBit2Lead = 0;
            j = BufferPointer + StopBitLength + StartBitLength - StartBitLead + NumberOfBits * NUMBEROFPROBES;
            //      j = BufferPointer+StopBit1Count + StartBitLength + NumberOfBits*NUMBEROFPROBES;
            for ( i = 0; i < StopBitLength;i++ )
            {
              f0sum += SF0[ ( j + i ) % SAMPLEBUFFERLENGTH];
              f1sum += SF1[ ( j + i ) % SAMPLEBUFFERLENGTH];

              if ( Demod[ ( j+i ) %SAMPLEBUFFERLENGTH] > 0 )
              {
                StopBit2Count++;
                StartBit2Lead = 0;
              }
              else
                StartBit2Lead++;
            }
            if ( StartBit2Lead > 2 )
              StartBit2Lead = 2;
            zf1sum = abs ( f1sum );
            zf0sum = abs ( f0sum );
            CalcQuality(abs(zf0sum-zf1sum)/(zf0sum+zf1sum));
            if ( ( ( ( StopBit1Count > ( 2* StopBitLength ) / 3 ) || ( StopBit1Value > 0 ) )
                   &&
                   ( ( StartBitCount > ( 2* NUMBEROFPROBES ) / 3 ) || ( StartBitValue < 0 ) ) )
                 ||
                 ( ( ( StartBitCount > ( 2* NUMBEROFPROBES ) / 3 ) || ( StartBitValue < 0 ) )
                   &&
                   ( ( StopBit2Count > ( 2* StopBitLength ) / 3 ) || ( zf1sum > zf0sum ) ) ) )
              Status = CollectingByte;
            else
            {
              Status = WaitingForMark;
              BufferCount -= ( StopBitLength - StopBit1Count + 1 );
            }
            break;
        }  // end of switch
      }
    }
  }
  if ( count < BUF_SIZE )
  {
    filteredSamples = 0;
    for ( i = count;i < BUF_SIZE;i++ )
      pIn[filteredSamples++] = complex<double> ( input[i], 0. );
  }
  else filteredSamples = 0;

}
void RTTYDemodulator::setRxFrequency ( double freq )
{
  if ( freq != RxFrequency )
  {
    RxFrequency = freq;
    F0 = PI2 * ( RxFrequency ) / SampleRate;
    F1 = PI2 * ( RxFrequency + extraParameter.offset ) / SampleRate;
    setFilter ( RxFrequency, RxFrequency + extraParameter.offset );
  }
}
void RTTYDemodulator::CalcQuality ( float x )
{
  ave2 = ave1;
  ave1 = 0.95 * ave1 + 0.05 * x;
// ave1 = 0.7*ave1 + 0.25 *ave2 + 0.05 *x;
}
///void RTTYDemodulator::CalcQuality ( int pointer )
///{
///  ave2 = ave1;
///  float sum, diff;
///  pointer = pointer % SAMPLEBUFFERLENGTH;
///
/////if ( sum > 0.1 )
///  ave1 = 0.7 * ave1 + 0.25 * ave2 + 0.05 * diff / sum;
/////else
///// ave1 = 0.5*ave1 + 0.3 *ave2;
///}

int RTTYDemodulator::getSquelchValue()
{
  return ( int ) ( 100.*ave1 );
}

double RTTYDemodulator::get2RxFrequency()
{
  return RxFrequency + extraParameter.offset;

}
void RTTYDemodulator::setParameter ( RxTxParameterType Type, void *Value )
{
  switch ( Type )
  {
    case Reverse:
      extraParameter.reverse =  * ( bool * ) Value;
      break;
    case Offset:
      extraParameter.offset = * ( int * ) Value;
      break;
    case Parity:
      extraParameter.parity = * ( Paritaet * ) Value;
      break;
    case Extra:
      extraParameter = * ( ExtraParameter * ) Value;
      break;
    default:
      break;
  }
}
void *RTTYDemodulator::getParameter ( RxTxParameterType Type )
{
  switch ( Type )
  {
    case Reverse:
      return ( void * ) &extraParameter.reverse;
      break;
    case Offset:
      return ( void * ) &extraParameter.offset;
      break;
    case Parity:
      return ( void * ) &extraParameter.parity;
      break;
    case Extra:
      return ( void * ) &extraParameter;
      break;
    default:
      return 0;
      break;
  }
}
void *RTTYDemodulator::getBuffer()
{
  return ( void * ) 0;
}
AfcMode RTTYDemodulator::AfcProperties()
{
  return Wide;
}
void RTTYDemodulator::setFilter ( double freq0, double freq1 )
{
  double x0, norm;
  int FilterLength, i;
  FilterLength = FFTFILTERLENGTH;
  double coeffs[FFTLENGTH];

//  x0 = ( PI2 * 15. ) / SampleRate;
  x0 = ( PI2 * 30. ) / SampleRate;
  for ( i = FilterLength;i < FFTLENGTH;i++ )
    coeffs[i] = 0.;
  for ( i = 0; i < FilterLength; i++ )
  {
    if ( i != ( FilterLength - 1 ) / 2 )
      coeffs[i] = sin ( x0 * ( i - ( FilterLength - 1 ) / 2 ) ) / ( i - ( FilterLength - 1 ) / 2 );
    else
      coeffs[i] = x0;
    coeffs[i] *= ( 0.42 - 0.5 * cos ( ( PI2 * i ) / ( FilterLength - 1 ) )
                   + 0.08 * cos ( ( PI2 * ( i + i ) ) / ( FilterLength - 1 ) ) );
  }
  norm = 0.;
  for ( i = 0;i < FilterLength;i++ )
    norm += coeffs[i];
  norm = ( norm * FilterLength ) / 10.;
  for ( i = 0;i < FilterLength;i++ )
    coeffs[i] /= norm;
  for ( i = 0;i < FFTLENGTH;i++ )
    pFilterIn[i] = coeffs[i] * exp ( complex<double> ( 0., PI2 * freq1 / SampleRate * ( i - ( FilterLength - 1 ) / 2 ) ) );
  for ( i = FilterLength;i < FFTLENGTH;i++ )
    pFilterIn[i] = complex<double> ( 0., 0. );

  fftw_execute ( pfilter );
  memcpy ( pFilterF1, pFilterF0, FFTLENGTH*sizeof ( complex<double> ) );

  for ( i = 0;i < FFTLENGTH;i++ )
    pFilterIn[i] = coeffs[i] * exp ( complex<double> ( 0., PI2 * freq0 / SampleRate * ( i - ( FilterLength - 1 ) / 2 ) ) );
  fftw_execute ( pfilter );
}
void RTTYDemodulator::execFilter()
{
  int i, overlapLength;
  overlapLength = FFTFILTERLENGTH - 1;

  for ( i = 0; i < FFTLENGTH;i++ )
  {
    pOutF1[i] = pOutF0[i] * pFilterF1[i];
    pOutF0[i] *= pFilterF0[i];
  }
  fftw_execute ( pbackwardF0 );
  fftw_execute ( pbackwardF1 );
  for ( i = 0;i < overlapLength;i++ )
  {
    pResultF0[i] += pOverlapF0[i];
    pResultF1[i] += pOverlapF1[i];
  }
  memcpy ( pOverlapF0, &pResultF0[RESULTLENGTH], sizeof ( fftw_complex ) * overlapLength );
  memcpy ( pOverlapF1, &pResultF1[RESULTLENGTH], sizeof ( fftw_complex ) * overlapLength );
}
void RTTYDemodulator::downmix ( complex<double> * z0, complex<double> *z1, int anzahl, int *y,
                                double *omegaF0, double *omegaF1 )
{
  int i, j, k;
  complex<double> sumf0, sumf1, zz0, zz1;
#define C1 0.8   /// 1. 0.1 0.7 , 0.775 0.1 0.7 , 0.8 0.08 0.575
//#define C3 0.05
#define C3 0.01
  double norm;
  k = 0;
  for ( i = 0;i < anzahl; i += DISTANCE )
  {
    sumf0 = 0.;
    sumf1 = 0.;
    for ( j = i;j < i + DISTANCE;j++ )
    {
      norm = abs ( z0[j] + z1[j] );
//      zz0    = ( z0[j] * exp ( complex<double> ( 0., -F0inc ) ) / norm ) ;
//      zz1    = ( z1[j] * exp ( complex<double> ( 0., -F1inc ) ) / norm ) ;
      z0[j] *= exp ( complex<double> ( 0., -F0inc ));
      zz0    = z0[j]/ norm  ;
      sumf0 += zz0;
      z1[j] *= exp ( complex<double> ( 0., -F1inc ));
      zz1    = z1[j]/ norm  ;
      sumf1 += zz1;

      F0inc += F0;
      if ( F0inc > PI2 )
        F0inc -= PI2;
      F1inc += F1;
      if ( F1inc > PI2 )
        F1inc -= PI2;
    }
    if ( F0max < abs ( zz0 ) )
      F0max = ( 1. - C1 ) * F0max + C1 * abs ( zz0 );
    else
      F0max = ( 1. - C3 ) * F0max - C3 * abs ( zz0 );
    if ( F1max < abs ( zz1 ) )
      F1max = ( 1. - C1 ) * F1max + C1 * abs ( zz1 );
    else
      F1max = ( 1. - C3 ) * F1max - C3 * abs ( zz1 );
    if ( ( abs ( sumf0 ) - 0.5 * F0max )  > ( abs ( sumf1 ) - 0.5*F1max ) )
      y[k] = -1;
    else
      y[k] = 1;

    omegaF0[k] = ( abs ( sumf0 ) - 0.5 * F0max );
    omegaF1[k++] = ( abs ( sumf1 ) - 0.5 * F1max );
  }
}

 

AMTOR

AMTOR is derived from ITU-R Recommendation M.476, and is known “narrowband direct printing” (NBDP) and commercially as “SITOR.” It has been largely overtaken by newer protocols.

AMTOR uses two forms of time diversity in either Mode A (ARQ, Automatic Repeat reQuest) or Mode B (FEC, Forward Error Correction). In Mode A, a repeat is sent only when requested by the receiving station. In Mode B, each character is sent twice. In Mode A or Mode B, the second type of time diversity is supplied by the redundancy of the code itself.

Mode B (FEC)

When transmitting to no particular station (for example calling CQ, in a net operation or during bulletin transmissions) there is no (one) receiving station to request repeats.

Mode B uses a simple forward-error-control (FEC) technique: It sends each character twice. Burst errors are virtually eliminated by delaying the repetition for a period thought to exceed the duration of most noise bursts. In AMTOR, groups of five characters are sent (DX) and then repeated (RX).

At 70 ms per character, there is 280 ms between the first and second transmissions of a character.

The Information Sending Station (ISS) transmitter must be capable of 100% dutycycle operation for Mode B. Thus, it may be necessary to reduce power level to 25% to 50% of full rating.

Mode A (ARQ)

This synchronous system transmits blocks of three characters from the Information Sending Station (ISS) to the Information Receiving Station (IRS). After each block, the IRS either acknowledges correct receipt (based on the 4/3 mark/space ratio) or requests a repeat. The station that initiates the ARQ protocol is known as the Master Station (MS). The MS first sends the selective call of the called station in blocks of three characters, listening between blocks. Four-letter AMTOR calls are normally derived from the first character and the last three letters of the station call sign. For example, W1AW’s AMTOR call would be WWAW. The Slave Station (SS) recognizes its selective call and answers that it is ready. The MS now becomes the ISS and will send traffic as soon as the IRS says it is ready.

On the air, AMTOR Mode A signals have a characteristic “chirp-chirp” sound. Because of the 210/240-ms on/off timing, Mode A can be used with some transmitters at full power levels.

 

AMTOR BIBLIOGRAPHY

Ford, Steve, WB8IMY, Your RTTY/AMTOR Companion (Newington, CT: ARRL, 1993.)

Henry, Bill, “Getting Started in Digital Communications- AMTOR,” QST, Jun 1992.

ITU-R Recommendations M.476 and 625, “Direct-Printing Telegraph Equipment in the Maritime Mobile Service.”

Martinez, Peter, “AMTOR, An Improved RTTY System Using a Microprocessor,” Radio Communication, RSGB, Aug 1979.

Newland, Paul, “A User’s Guide to AMTOR Operation,” QST, Oct 1985.

 

Examine the following code written in C. For offline decoding you do not need to use all code. Some parts of the code are unnecessary but then you need to rewrite the code by modifying the code (amtor.c, fskl1.h, fskutil.h). The most critical part of the code is amtor_monitor_arq() which gives direct way of protocol’s on-working structure.

/*****************************************************************************/

/*
 *      amtor.c  --  Amtor protocol.
 *
 *      Copyright (C) 1996  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *        Swiss Federal Institute of Technology (ETH), Electronics Lab
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h> 
#include <assert.h>
#include <syslog.h>
#include <string.h>
#include <sys/types.h>

#include "fskl1.h"
#include "fskutil.h"
#include "msg.h"
#include "main.h"
#include "amtor.h"
#include "standby.h"

/* --------------------------------------------------------------------- */

#define AMTOR_CS1   0x12
#define AMTOR_CS2   0x00
#define AMTOR_CS3   0x0c
#define AMTOR_BETA  0x20
#define AMTOR_ALPHA 0x21
#define AMTOR_RQ    0x22
#define AMTOR_PS1   AMTOR_ALPHA
#define AMTOR_PS2   AMTOR_RQ

#define RXOVERSAMPLING 8

/* --------------------------------------------------------------------- */

static const short amtor_check_table[128] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 33,
        -1, -1, -1, -1, -1, -1, -1, 11, -1, -1, -1, 13, -1, 14, 15, -1,
        -1, -1, -1, -1, -1, -1, -1, 19, -1, -1, -1, 21, -1, 22, 23, -1,
        -1, -1, -1, 32, -1, 26, 27, -1, -1, 28, 29, -1, 30, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1,  3, -1, -1, -1,  5, -1,  6,  7, -1,
        -1, -1, -1,  9, -1, 10,  1, -1, -1, 12, 31, -1,  4, -1, -1, -1,
        -1, -1, -1, 17, -1, 18, 34, -1, -1, 20,  0, -1,  2, -1, -1, -1,
        -1, 24, 25, -1, 16, -1, -1, -1,  8, -1, -1, -1, -1, -1, -1, -1
};

static const unsigned char amtor_encode_table[35] = {
        0x6a, 0x56, 0x6c, 0x47, 0x5c, 0x4b, 0x4d, 0x4e, 0x78, 0x53, 0x55, 0x17, 0x59, 0x1b, 0x1d, 0x1e,
        0x74, 0x63, 0x65, 0x27, 0x69, 0x2b, 0x2d, 0x2e, 0x71, 0x72, 0x35, 0x36, 0x39, 0x3a, 0x3c, 0x5a,
        0x33, 0x0f, 0x66
};

/* --------------------------------------------------------------------- */

#define SHIFT_NUMBER    27
#define SHIFT_LETTER    31
#define SHIFT_LOWERCASE  0

#define IDLE_SHIFT_FREQ 10

#define SYMBOL_PLUS  17
#define SYMBOL_QMARK 25

/* --------------------------------------------------------------------- */

#define INVAL "\001"
#define BELL  "\007" 
#define WRU   "$"

#define LETTERS INVAL "E\nA SIU\rDRJNFCKTZLWHYPQOBG" INVAL "MXV" INVAL
#define NUMBERS_DL INVAL"3\n- '87\r" WRU "4ß,Ä:(5+)2Ü6019?Ö" INVAL "./=" INVAL
#define NUMBERS_GB INVAL"3\n- '87\r" WRU "4" BELL ",%:(5+)2#6019?@" INVAL "./=" INVAL
#define NUMBERS_USA INVAL "3\n- " BELL "87\r" WRU "4',!:(5\")2*6019?&" INVAL "./;" INVAL

static const char amtor_chars[65] = LETTERS NUMBERS_DL;

#define LETTER_MASK 0x33fffeea
#define IS_LETTER(x) ((LETTER_MASK >> (x)) & 1)

#define FREQ_TRACKING_DIST 10 /* Hz */
#define FREQ_TRACKING

/* --------------------------------------------------------------------- */

struct {
	unsigned char destcall[4];
	unsigned char selfeccall[4];
	unsigned char mycall[4];
	int rxinvert;
	int txinvert;
	unsigned int retry;
	unsigned int txdelay;
} ap = { "\0\0\0\0", "\0\0\0\0", "\0\0\0\0", 0, 0, 30, 30000 };

#define MONNUM 8

struct {
	unsigned int flags;
	unsigned int figmode;
	unsigned int lcflag;
	unsigned int last_ch;
	l1_time_t rxtime;
	l1_time_t txtime;
	int rxfreqdev, txfreqdev;
	unsigned int is_master;
	int retry;

	l1_time_t mon_mute;

	struct {
		long devflt[8];
		unsigned int ptr;
	} tm;

	struct {
		int state;
		l1_time_t tm;
	} mon[MONNUM];

	unsigned char txtriple[3];
	l1_soft_t rxbuf[56*RXOVERSAMPLING];
	unsigned char txbuf[8];
#ifdef FREQ_TRACKING
	struct {
		l1_soft_t rxb1[56];
		l1_soft_t rxb2[56];
	} trk;
#endif /* FREQ_TRACKING */
} as;

/* --------------------------------------------------------------------- */
/*
 * Timing functions
 */

#define TMSIZE (sizeof(as.tm.devflt)/sizeof(as.tm.devflt[0]))

extern __inline__ void tmg_clear(void)
{
	memset(as.tm.devflt, 0, sizeof(as.tm.devflt));
	as.tm.ptr = 0;
}

static void tmg_add(long dev)
{
	long acc = 0;
	int i;

	as.tm.devflt[as.tm.ptr++] =  dev;
#if 0
	printf("Timing deviation: %ld  ptr: %d  smooth: %ld %ld %ld %ld %ld %ld %ld %ld\n", dev, as.tm.ptr,
	       as.tm.devflt[0], as.tm.devflt[1], as.tm.devflt[2], as.tm.devflt[3], as.tm.devflt[4], 
	       as.tm.devflt[5], as.tm.devflt[6], as.tm.devflt[7]);
#endif
	as.tm.ptr %= TMSIZE;
	for (i = 0; i < TMSIZE; i++)
		acc += as.tm.devflt[i];
	acc /= (signed)TMSIZE;
	if (!acc)
		return;
	for (i = 0; i < TMSIZE; i++)
		as.tm.devflt[i] -= acc;
#if 1
	as.rxtime += acc;
	if (!as.is_master)
		as.txtime += acc;
#endif
	bufprintf(HFAPP_MSG_DATA_MONITOR, "Timing correction: %ld\n", acc);
}

/* --------------------------------------------------------------------- */

static int setcall(unsigned char *call, const char *in)
{
	int i;
	char c;
	unsigned char buf[4];

	for (i = 0; i < 4; i++, in++) {
		c = *in;
		if (c >= 'a' && c <= 'z')
			c -= 'a'-'A';
		if (c < 'A' || c > 'Z')
			return 0;
		buf[i] = (char *)memchr(amtor_chars, c, 32) - amtor_chars;
	}
	memcpy(call, buf, 4);
	return 1;
}

/* --------------------------------------------------------------------- */

void amtor_set_params(const unsigned char *destcall, const unsigned char *selfeccall,
		      const unsigned char *mycall, unsigned int txdelay,
		      unsigned int retry, int rxinvert, int txinvert)
{
	errprintf(SEV_INFO, "amtor params: destcall %-4.4s selfeccall %-4.4s mycall %-4.4s "
		  "txdelay %dms retries %d invert%s%s\n", destcall, selfeccall, mycall, 
		  txdelay, retry, rxinvert ? " RX" : "", txinvert ? " TX" : "");
	if (!setcall(ap.destcall, destcall))
		errprintf(SEV_WARNING, "invalid destination call\n");
	if (!setcall(ap.selfeccall, selfeccall))
		errprintf(SEV_WARNING, "invalid selective FEC call\n");
	if (!setcall(ap.mycall, mycall))
		errprintf(SEV_WARNING, "invalid mycall\n");
	ap.rxinvert = !!rxinvert;
	ap.txinvert = !!txinvert;
	ap.retry = retry;
	ap.txdelay = txdelay * 1000;
}

/* --------------------------------------------------------------------- */

static const char *monitor_names[35] = {
	"LCS", "E", "LF", "A", "SP", "S", "I", "U", "CR", "D", "R", "J",
	"N", "F", "C", "K", "T", "Z", "L", "W", "H", "Y", "P", "Q", "O", "B",
	"G", "FS", "M", "X", "V", "LS", "BETA", "ALPHA", "RQ"
};

static void monitor_triple(const char *name, unsigned short s1, unsigned short s2, unsigned short s3)
{

	if (s1 >= 35 || s2 >= 35 || s3 >= 35)
		return;
	bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: %sTriple: %s_%s_%s\n", name, 
		  monitor_names[s1], monitor_names[s2], monitor_names[s3]);
}

static void output_char(unsigned short c)
{
	char ch;

	if (c >= 32)
		return;
	switch (c) {
	case SHIFT_LETTER:
		as.figmode = 0;
		as.lcflag = 0;
		break;

	case SHIFT_NUMBER:
		as.figmode = 0x20;
		as.lcflag = 0;
		break;

	case SHIFT_LOWERCASE:
		as.lcflag = !as.lcflag;
		break;

	default:
		ch = amtor_chars[as.figmode + c];
		if (as.lcflag && ch >= 'A' && ch <= 'Z')
			ch += 'a' - 'A';
		bufwrite(HFAPP_MSG_DATA_RECEIVE, &ch, 1);
	}
}

static void output_triple(unsigned short s1, unsigned short s2, unsigned short s3)
{
	output_char(s1);
	output_char(s2);
	output_char(s3);
}

/* --------------------------------------------------------------------- */

#define FLG_QRT       (1<<0)
#define FLG_BECOMEISS (1<<1)
#define FLG_BECOMEIRS (1<<2)

void amtor_mode_qrt(void)
{
	as.flags |= FLG_QRT;
}

void amtor_mode_irs(void)
{
	as.flags |= FLG_BECOMEIRS;
}

void amtor_mode_iss(void)
{
	as.flags |= FLG_BECOMEISS;
}

/* --------------------------------------------------------------------- */

void amtor_reset_uppercase(void)
{
	as.figmode = 0;
	as.lcflag = 0;
}

void amtor_reset_lowercase(void)
{
	as.figmode = 0;
	as.lcflag = 1;
}

void amtor_reset_figurecase(void)
{
	as.figmode = 0x20;
}

/* --------------------------------------------------------------------- */

static unsigned short get_tx_char(void)
{
	unsigned short s;
	unsigned char *bp;
	unsigned int lc;
	unsigned int nr;

	for (;;) {
		kbd_negack();
		if ((s = kbd_get()) == KBD_EOF) {
			kbd_ack(); // by günther, 
				    // ack of end of data must be for autorx
			return AMTOR_BETA;
		}
		s &= KBD_CHAR;
		if (s >= 'a' && s <= 'z') {
			lc = 1;
			s -= 'a' - 'A';
		} else if (s >= 'A' && s <= 'Z')
			lc = 0;
		else
			lc = as.lcflag;
		if (!(bp = memchr(amtor_chars, s, 64))) {
			kbd_ack();
			continue;
		}
		nr = bp - (unsigned char *)amtor_chars;
		if ((nr & 0x20) != as.figmode) {
			as.figmode = nr & 0x20;
			as.lcflag = 0;
			return as.figmode ? SHIFT_NUMBER : SHIFT_LETTER;
		}
		if (lc != as.lcflag) {
			as.lcflag = lc;
			return SHIFT_LOWERCASE;
		}
		kbd_ack();
		return nr - as.figmode;
	}
}

/* --------------------------------------------------------------------- */
/*
 * frequency tracking utility functions
 */

#ifdef FREQ_TRACKING
extern __inline__ void amtor_freq_tracking(int trk, l1_soft_t trkl, l1_soft_t trkm, l1_soft_t trkh)
{
	as.rxfreqdev += trk;
	if (!as.is_master)
		as.txfreqdev += trk;
	bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TRACKING: %d  %d/%d/%d  FREQDEVIATION: %d/%d\n", 
		  trk, trkl, trkm, trkh, as.rxfreqdev, as.txfreqdev);
}
#endif /* FREQ_TRACKING */

/* --------------------------------------------------------------------- */

static void fec_rx_cont(const char *name, unsigned int msg, l1_id_t id, int inv)
{
	l1_soft_t *s;
	unsigned char chdx, chrx;
	short sym_dx, sym_rx, sym_cdx;
#ifdef FREQ_TRACKING
	l1_soft_t trkm, trkl, trkh;
	int trk = 0;
#endif /* FREQ_TRACKING */

	errprintf(SEV_INFO, "%s\n", name);
	send_short_msg(msg, ERR_NOERR);
	as.retry = ap.retry;
	l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100,
			  id, 14*RXOVERSAMPLING, as.rxbuf+14*id*RXOVERSAMPLING);
#ifdef FREQ_TRACKING
	l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev-FREQ_TRACKING_DIST, 100, 
			  0x210+2*id, 14, as.trk.rxb1+14*id);
	l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev+FREQ_TRACKING_DIST, 100, 
			  0x211+2*id, 14, as.trk.rxb2+14*id);
#endif /* FREQ_TRACKING */
	as.rxtime += 140000;
	for (;;) {
		id = l1_fsk_wait_request();
		if (id < 0 || id >= 4)
			continue;
		s = as.rxbuf+14*id*RXOVERSAMPLING;
		tmg_add(soft_time_dev(s, 14, RXOVERSAMPLING, 1000000/100/RXOVERSAMPLING));
#ifdef FREQ_TRACKING
		trk = freq_tracking(s, as.trk.rxb1+14*id, as.trk.rxb2+14*id, 14, RXOVERSAMPLING, &trkm, &trkl, &trkh);
		amtor_freq_tracking(trk, trkl, trkm, trkh);
#endif /* FREQ_TRACKING */
		soft_to_hard(s, &chdx, 7, RXOVERSAMPLING, inv);
		soft_to_hard(s+7*RXOVERSAMPLING, &chrx, 7, RXOVERSAMPLING, inv);
		as.last_ch = (as.last_ch << 8) | chdx;
		sym_dx = amtor_check_table[(as.last_ch >> 16) & 0x7f];
		sym_rx = amtor_check_table[chrx & 0x7f];
		sym_cdx = amtor_check_table[chdx & 0x7f];
		if (sym_dx >= 0) {
			if (sym_rx == AMTOR_PS1 && sym_dx == AMTOR_PS2)
				as.retry += 2;
			else if (sym_rx == AMTOR_PS2 || sym_dx == AMTOR_PS1)
				as.retry -= 8;
			else if (sym_rx >= 0 && sym_rx != sym_dx && sym_rx != AMTOR_PS1) 
				as.retry -= 8;
			else if (sym_rx >= 0)
				as.retry += 2;
			if (as.retry > ap.retry)
				as.retry = ap.retry;
			output_char(sym_dx);
		} else if (sym_rx >= 0)
			output_char(sym_rx);
		else {
			as.retry--;
			bufprintf(HFAPP_MSG_DATA_RECEIVE, "_"); /* error symbol */
		}
		if ((as.last_ch & 0x7f) == amtor_encode_table[AMTOR_ALPHA] && 
		    ((as.last_ch >> 8) & 0x7f) == amtor_encode_table[AMTOR_ALPHA]) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_FEC_DISCONNECT, ERR_NOERR);
			return;
		}			
		if (as.retry < 0) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_FEC_DISCONNECT, ERR_TIMEOUT);
			return;
		}
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FECRX: %s_%s  (%02x_%02x)\n", 
			  sym_cdx >= 0 ? monitor_names[sym_cdx] : "?",
			  sym_rx >= 0 ? monitor_names[sym_rx] : "?",
			  chdx & 0x7f, chrx & 0x7f);
		l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100,
				  id, 14*RXOVERSAMPLING, s);
#ifdef FREQ_TRACKING
		l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev-FREQ_TRACKING_DIST, 100, 
				  0x210+2*id, 14, as.trk.rxb1+14*id);
		l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev+FREQ_TRACKING_DIST, 100, 
				  0x211+2*id, 14, as.trk.rxb2+14*id);
#endif /* FREQ_TRACKING */
		as.rxtime += 140000;
//		bufprintf(HFAPP_MSG_DATA_STATUS, "%s\nRetry: %d\n", name, as.retry);
	}
}

/* --------------------------------------------------------------------- */

static void fec_rx(void)
{
	l1_soft_t *s;
	l1_id_t id;
	unsigned char chdx, chrx;
	short sym_dx, sym_cdx, sym_rx, sym_idx, sym_irx;
	unsigned short selfcall[5];
#ifdef FREQ_TRACKING
	l1_soft_t trkm, trkl, trkh;
	int trk = 0;
#endif /* FREQ_TRACKING */

	l1_fsk_clear_requests();
	errprintf(SEV_INFO, "mode: amtor fec rx\n");
	kbd_clear_and_fill(NULL, 0);
	as.retry = ap.retry;
	as.figmode = 0;
	as.lcflag = 0;
	as.is_master = 0;
	tmg_clear();
	for (id = 0; id < 4; id++) {
		l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100,
			      id, 14*RXOVERSAMPLING, as.rxbuf+14*id*RXOVERSAMPLING);
#ifdef FREQ_TRACKING
		l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev-FREQ_TRACKING_DIST, 100, 
				  0x210+2*id, 14, as.trk.rxb1+14*id);
		l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev+FREQ_TRACKING_DIST, 100, 
				  0x211+2*id, 14, as.trk.rxb2+14*id);
#endif /* FREQ_TRACKING */
		as.rxtime += 140000;
	}
	for (;;) {
		id = l1_fsk_wait_request();
		if (id < 0 || id >= 4)
			continue;
		s = as.rxbuf+14*id*RXOVERSAMPLING;
		tmg_add(soft_time_dev(s, 14, RXOVERSAMPLING, 1000000/100/RXOVERSAMPLING));
#ifdef FREQ_TRACKING
		trk = freq_tracking(s, as.trk.rxb1+14*id, as.trk.rxb2+14*id, 14, RXOVERSAMPLING, &trkm, &trkl, &trkh);
		amtor_freq_tracking(trk, trkl, trkm, trkh);
#endif /* FREQ_TRACKING */
		soft_to_hard(s, &chdx, 7, RXOVERSAMPLING, ap.rxinvert);
		soft_to_hard(s+7*RXOVERSAMPLING, &chrx, 7, RXOVERSAMPLING, ap.rxinvert);
		as.last_ch = (as.last_ch << 8) | chdx;
		sym_dx = amtor_check_table[(as.last_ch >> 16) & 0x7f];
		sym_rx = amtor_check_table[chrx & 0x7f];
		sym_cdx = amtor_check_table[chdx & 0x7f];
		sym_idx = amtor_check_table[(~as.last_ch >> 16) & 0x7f];
		sym_irx = amtor_check_table[(~chrx) & 0x7f];
		selfcall[4] = selfcall[3];
		selfcall[3] = selfcall[2];
		selfcall[2] = selfcall[1];
		selfcall[1] = selfcall[0];
		if (sym_irx >= 0) 
			selfcall[0] = sym_irx;
		else if (sym_idx >= 0)
			selfcall[0] = sym_idx;
		else 
			selfcall[0] = -1;
		if (selfcall[0] == AMTOR_BETA && selfcall[4] == ap.selfeccall[0] && selfcall[3] == ap.selfeccall[1] &&
		    selfcall[2] == ap.selfeccall[2] && selfcall[1] == ap.selfeccall[3]) {
			bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: Selective FEC call received\n");
			as.last_ch = ~as.last_ch;
			fec_rx_cont("AMTOR SELECTIVE FEC RX", HFAPP_MSG_STATE_AMTOR_SELFEC_RX, id, !ap.rxinvert);
			return;
		}
		if (selfcall[0] == AMTOR_BETA && IS_LETTER(selfcall[4]) && IS_LETTER(selfcall[3]) && 
		    IS_LETTER(selfcall[2]) && IS_LETTER(selfcall[1]))
			bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: Selective FEC call %c%c%c%c\n",
				  amtor_chars[selfcall[4]], amtor_chars[selfcall[3]], 
				  amtor_chars[selfcall[2]], amtor_chars[selfcall[1]]);
		if (sym_dx >= 0 || sym_rx >= 0) {
			output_char(sym_dx >= 0 ? sym_dx : sym_rx);
			if (sym_dx >= 0 && sym_dx < 32 && sym_dx == sym_rx) {
				fec_rx_cont("AMTOR COLLECTIVE FEC RX", HFAPP_MSG_STATE_AMTOR_COLFEC_RX, id, ap.rxinvert);
				return;
			}
		}
		if (sym_rx == AMTOR_PS2 || sym_dx == AMTOR_PS1)
			return;
		if (sym_rx < 0 || sym_dx < 0)
			if ((--as.retry) < 0) 
				return;
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FECRX: %s_%s  (%02x_%02x)\n", 
			  sym_cdx >= 0 ? monitor_names[sym_cdx] : "?",
			  sym_rx >= 0 ? monitor_names[sym_rx] : "?",
			  chdx & 0x7f, chrx & 0x7f);
		l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100,
				  id, 14*RXOVERSAMPLING, s);
#ifdef FREQ_TRACKING
		l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev-FREQ_TRACKING_DIST, 100, 
				  0x210+2*id, 14, as.trk.rxb1+14*id);
		l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev+FREQ_TRACKING_DIST, 100, 
				  0x211+2*id, 14, as.trk.rxb2+14*id);
#endif /* FREQ_TRACKING */
		as.rxtime += 140000;
		bufprintf(HFAPP_MSG_DATA_STATUS, "AMTOR FEC RECEIVE START\nRetry: %d\n", as.retry);
	}
}

/* --------------------------------------------------------------------- */
/*
 * misc utility functions
 */

static void send_cs(int cs)
{
	static unsigned int csidx[4] = { AMTOR_CS1, AMTOR_CS2, AMTOR_CS3, AMTOR_RQ };

	assert(cs >= 1 && cs <= 4);
	as.txbuf[0] = amtor_encode_table[csidx[cs-1]];
	l1_fsk_tx_request(as.txtime-ap.txdelay, ap.txdelay, as.txfreqdev, 0, 0x100, 1, as.txbuf);
	l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, ap.txinvert, 0x101, 7, as.txbuf);
	if (cs == 4) 
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: RQ\n");
	else
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: CS%c\n", '0'+cs);
}

static void send_triple(int mode)
{
	unsigned int bits;

	l1_fsk_tx_request(as.txtime-ap.txdelay, ap.txdelay, as.txfreqdev, 0, 0x100, 1, as.txbuf);
	if (mode == 2) {
		bits = amtor_encode_table[AMTOR_BETA] |
			(amtor_encode_table[AMTOR_ALPHA] << 7) |
			(amtor_encode_table[AMTOR_BETA] << 14);
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: BETA_ALPHA_BETA\n");
	} else if (mode) {
		bits = amtor_encode_table[AMTOR_RQ];
		bits |= (bits << 7) | (bits << 14);
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: RQ_RQ_RQ\n");
	} else {
		bits = amtor_encode_table[as.txtriple[0]] |
			(amtor_encode_table[as.txtriple[1]] << 7) |
			(amtor_encode_table[as.txtriple[2]] << 14);
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: %s_%s_%s\n", monitor_names[as.txtriple[0]],
			  monitor_names[as.txtriple[1]], monitor_names[as.txtriple[2]]);
	}
	as.txbuf[0] = bits & 0xff;
	as.txbuf[1] = (bits >> 8) & 0xff;
	as.txbuf[2] = (bits >> 16) & 0xff;
	l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, ap.txinvert, 0x101, 21, as.txbuf);
}

static int receive_cs(void)
{
	unsigned char csbuf;
	long dev;
	short sym;
#ifdef FREQ_TRACKING
	l1_soft_t trkm, trkl, trkh;
	int trk;
#endif /* FREQ_TRACKING */

	l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100, 0x200,
			  7*RXOVERSAMPLING, as.rxbuf);
#ifdef FREQ_TRACKING
	l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev-FREQ_TRACKING_DIST, 100, 0x210, 7, as.trk.rxb1);
	l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev+FREQ_TRACKING_DIST, 100, 0x211, 7, as.trk.rxb2);
#endif /* FREQ_TRACKING */
	while (l1_fsk_wait_request() != 0x200);
	soft_to_hard(as.rxbuf, &csbuf, 7, RXOVERSAMPLING, ap.rxinvert);
	sym = amtor_check_table[csbuf & 0x7f];
	dev = soft_time_dev(as.rxbuf, 7, RXOVERSAMPLING, 1000000/100/RXOVERSAMPLING);
	tmg_add(dev);
#ifdef FREQ_TRACKING
	trk = freq_tracking(as.rxbuf, as.trk.rxb1, as.trk.rxb2, 7, RXOVERSAMPLING, &trkm, &trkl, &trkh);
	amtor_freq_tracking(trk, trkl, trkm, trkh);
#endif /* FREQ_TRACKING */
	if (sym == AMTOR_CS1) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: CS1 dT=%ld\n", dev);
		return 1;
	}
	if (sym == AMTOR_CS2) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: CS2 dT=%ld\n", dev);
		return 2;
	}
	if (sym == AMTOR_CS3) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: CS3 dT=%ld\n", dev);
		return 3;
	}
	if (sym == AMTOR_RQ) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: RQ dT=%ld\n", dev);
		return 4;
	}
	bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: CS?  sym: %2d  bits: %02x  dT=%ld\n", sym, csbuf & 0x7f, dev);
	return 0;
}

#define PKT_OK      (1<<0)
#define PKT_QRT     (1<<1)
#define PKT_BRKIN   (1<<2)
#define PKT_OVER    (1<<3)
#define PKT_REQUEST (1<<4)
#define PKT_IDLE    (1<<5)

static int receive_triple(void)
{
	long dev;
	unsigned char trp[3];
	unsigned short s1, s2, s3;
	int retval = PKT_OK;
#ifdef FREQ_TRACKING
	l1_soft_t trkm, trkl, trkh;
	int trk = 0;
#endif /* FREQ_TRACKING */

	l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100, 
			  0x200, 21*RXOVERSAMPLING, as.rxbuf);
#ifdef FREQ_TRACKING
	l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev-FREQ_TRACKING_DIST, 100, 0x210, 21, as.trk.rxb1);
	l1_fsk_rx_request(as.rxtime, 1000000/100, as.rxfreqdev+FREQ_TRACKING_DIST, 100, 0x211, 21, as.trk.rxb2);
#endif /* FREQ_TRACKING */
	while (l1_fsk_wait_request() != 0x200);
	dev = soft_time_dev(as.rxbuf, 21, RXOVERSAMPLING, 1000000/100/RXOVERSAMPLING);
	tmg_add(dev);
#ifdef FREQ_TRACKING
	trk = freq_tracking(as.rxbuf, as.trk.rxb1, as.trk.rxb2, 21, RXOVERSAMPLING, &trkm, &trkl, &trkh);
	amtor_freq_tracking(trk, trkl, trkm, trkh);
#endif /* FREQ_TRACKING */
	soft_to_hard(as.rxbuf, trp, 21, RXOVERSAMPLING, ap.rxinvert);
	s1 = amtor_check_table[trp[0] & 0x7f];
	s2 = amtor_check_table[((trp[0] >> 7) | (trp[1] << 1)) & 0x7f];
	s3 = amtor_check_table[((trp[1] >> 6) | (trp[2] << 2)) & 0x7f];
	if (s1 >= 35 || s2 >= 35 || s3 >= 35) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: %s_%s_%s   %02x%02x%02x  dT=%ld\n", 
			  (s1 < 35) ? monitor_names[s1] : "?", (s2 < 35) ? monitor_names[s2] : "?",
			  (s3 < 35) ? monitor_names[s3] : "?", trp[2] & 0x1f, trp[1], trp[0], dev);
		return 0;
	}
	monitor_triple("RX: ", s1, s2, s3);
	if (s1 == AMTOR_RQ || s2 == AMTOR_RQ || s3 == AMTOR_RQ)
		return PKT_REQUEST;
	if (s1 == AMTOR_ALPHA && s2 == AMTOR_ALPHA && s3 == AMTOR_ALPHA)
		return PKT_QRT;
	if (s1 == AMTOR_BETA && s2 == AMTOR_ALPHA && s3 == AMTOR_BETA)
		return PKT_OVER;
	if ((s1 >= 32 && s1 != AMTOR_BETA) || (s2 >= 32 && s2 != AMTOR_BETA) || 
	    (s3 >= 32 && s3 != AMTOR_BETA))
		return 0;
	if (s1 == AMTOR_BETA || s2 == AMTOR_BETA || s3 == AMTOR_BETA)
		retval |= PKT_IDLE;
	output_triple(s1, s2, s3);
	as.last_ch = (as.last_ch << 8) | (s1 & 0xff);
	if ((as.last_ch & 0xffffff) == ((SHIFT_NUMBER << 16) | (SYMBOL_PLUS << 8) | SYMBOL_QMARK))
		retval |= PKT_BRKIN;
	as.last_ch = (as.last_ch << 8) | (s2 & 0xff);
	if ((as.last_ch & 0xffffff) == ((SHIFT_NUMBER << 16) | (SYMBOL_PLUS << 8) | SYMBOL_QMARK))
		retval |= PKT_BRKIN;
	as.last_ch = (as.last_ch << 8) | (s3 & 0xff);
	if ((as.last_ch & 0xffffff) == ((SHIFT_NUMBER << 16) | (SYMBOL_PLUS << 8) | SYMBOL_QMARK))
		retval |= PKT_BRKIN;
	return retval;
}

static int encode_triple(void)
{
	if (as.flags & FLG_BECOMEIRS) {
		as.flags &= ~FLG_BECOMEIRS;
		as.txtriple[0] = SHIFT_NUMBER;
		as.txtriple[1] = SYMBOL_PLUS;
		as.txtriple[2] = SYMBOL_QMARK;
		return PKT_BRKIN;
	}
	if (as.flags & FLG_QRT) {
		as.flags &= ~FLG_QRT;
		as.txtriple[0] = AMTOR_ALPHA;
		as.txtriple[1] = AMTOR_ALPHA;
		as.txtriple[2] = AMTOR_ALPHA;
		return PKT_QRT;
	}
	as.txtriple[0] = get_tx_char();
	as.txtriple[1] = get_tx_char();
	as.txtriple[2] = get_tx_char();
	return 0;
}

/* --------------------------------------------------------------------- */

extern __inline__ void cycle_end(void)
{
	as.rxtime += 450000;
	as.txtime += 450000;
}

extern __inline__ int retry(void)
{
	if ((--as.retry) <= 0)
		return 1;
	return 0;
}

/* --------------------------------------------------------------------- */

static void disp_status(const char *modename)
{
// was STATUS, changed by Günther
	bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR %s\n%s rt:%d df:%d/%d\nCMD:%s%s%s\n", modename, 
		  as.is_master ? "MASTER" : "SLAVE", as.retry, as.txfreqdev, as.rxfreqdev,
		  (as.flags & FLG_QRT) ? " QRT" : "", (as.flags & FLG_BECOMEIRS) ? " IRS" : "", 
		  (as.flags & FLG_BECOMEISS) ? " ISS" : "");
}

/* --------------------------------------------------------------------- */

static void arq_statemachine(void)
{
	int eflg, i, rq;

	l1_fsk_clear_requests();
	kbd_clear_and_fill(NULL, 0);
	as.flags = 0;
	as.figmode = 0;
	as.lcflag = 0;
	as.last_ch = 0;

	if (as.is_master) {
		send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_MASTERCONNECT, ERR_NOERR);
		goto tx_cs1;
	}
	send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_SLAVECONNECT, ERR_NOERR);
	goto rx_cs1;

tx_cs1:
	as.retry = ap.retry;
	eflg = encode_triple();
	rq = 0;
	for (;;) {
		send_triple(rq);
		disp_status("ARQ TX CS1");
		cycle_end();
		i = receive_cs();
		if (i == 2) {
			if (eflg & PKT_QRT) {
				send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_NOERR);
				return;
			}
			goto tx_cs2;
		} else if (i == 3)
			goto tx_rx;
		else if (i != 1 && retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
		rq = i != 1;
	}

tx_cs2:
	as.retry = ap.retry;
	eflg = encode_triple();
	rq = 0;
	for (;;) {
		send_triple(rq);
		disp_status("ARQ TX CS2");
		cycle_end();
		i = receive_cs();
		if (i == 1) {
			if (eflg & PKT_QRT) {
				send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_NOERR);
				return;
			}
			goto tx_cs1;
		} else if (i == 3)
			goto tx_rx;
		else if (i != 2 && retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
		rq = i != 2;
	}

tx_rx:
	as.retry = ap.retry;
	as.flags &= ~FLG_BECOMEIRS;
	send_short_msg(HFAPP_MSG_STATE_AMTOR_IRS, ERR_NOERR);
	for (;;) {
		send_triple(2);
		disp_status("ARQ TX->RX");
		cycle_end();
		i = receive_cs();
		if (i == 4) {
			if (as.is_master)
				as.txtime += 140000;
			else
				as.rxtime -= 140000;
			goto rx_cs1;
		} else if (i != 3 && retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
	}

rx_cs1:
	as.retry = ap.retry;
	for (;;) {
		send_cs(1);
		disp_status("ARQ RX CS1");
		cycle_end();
		i = receive_triple();
		if (i & PKT_QRT) 
			goto rx_qrt_cs2;
		if ((i & PKT_BRKIN) || (i && (as.flags & FLG_BECOMEISS)))
			goto rx_tx_1;
		if (i & PKT_OK)
			goto rx_cs2;
		if (!i  && retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
	}

rx_cs2:
	as.retry = ap.retry;
	for (;;) {
		send_cs(2);
		disp_status("ARQ RX CS2");
		cycle_end();
		i = receive_triple();
		if (i & PKT_QRT)
			goto rx_qrt_cs1;
		if ((i & PKT_BRKIN) || (i && (as.flags & FLG_BECOMEISS)))
			goto rx_tx_1;
		if (i & PKT_OK)
			goto rx_cs1;
		if (!i  && retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
	}

rx_qrt_cs1:
	as.retry = 5;
	send_cs(1);
	for (;;) {
		disp_status("ARQ RX QRT CS1");
		cycle_end();
		i = receive_triple();
		if (i & PKT_QRT) {
			send_cs(1);
			as.retry = 5;
		} else if (retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
	}

rx_qrt_cs2:
	as.retry = 5;
	send_cs(2);
	for (;;) {
		disp_status("ARQ RX QRT CS1");
		cycle_end();
		i = receive_triple();
		if (i & PKT_QRT) {
			send_cs(2);
			as.retry = 5;
		} else if (retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
	}

rx_tx_1:
	as.retry = ap.retry;
	as.flags &= ~FLG_BECOMEISS;
	send_short_msg(HFAPP_MSG_STATE_AMTOR_ISS, ERR_NOERR);
	for (;;) {
		send_cs(3);
		disp_status("ARQ RX->TX 1");
		cycle_end();
		i = receive_triple();
		if (i & PKT_OVER)
			goto rx_tx_2;
		if (!i  && retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
	}

rx_tx_2:
	if (as.is_master) {
		send_triple(1);
		as.txtime -= 140000;
		disp_status("ARQ RX->TX 2 (MASTER)");
		cycle_end();
		i = receive_cs();
		if (i == 1) 
			goto tx_cs1;
	} else {
		as.rxtime += 140000;
	}
	as.retry = ap.retry;
	send_short_msg(HFAPP_MSG_STATE_AMTOR_ISS, ERR_NOERR);
	for (;;) {
		send_triple(1);
		disp_status("ARQ RX->TX 2");
		cycle_end();
		i = receive_cs();
		if (i == 1) 
			goto tx_cs1;
		else if (i == 3)
			goto tx_rx;
		if (retry()) {
			send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
			return;
		}
	}
}

/* --------------------------------------------------------------------- */

void amtor_monitor_init(l1_time_t tm)
{
	int i;

	as.mon_mute = tm - 440000;
	for (i = 0; i < MONNUM; i++)
		as.mon[i].state = -1;
}

/* --------------------------------------------------------------------- */

int amtor_monitor_arq(l1_time_t tm, unsigned char pk01, unsigned char pk02, 
		      unsigned char pk03, unsigned char pk11, unsigned char pk12,
		      unsigned char pk13, l1_soft_t *samples, int freqdev)
{
	short t01, t02, t03, t11, t12, t13;
	long dev;
	int i, j, k;

	if (ap.rxinvert) {
		pk01 ^= 0x7f;
		pk02 ^= 0x7f;
		pk03 ^= 0x7f;
		pk11 ^= 0x7f;
		pk12 ^= 0x7f;
		pk13 ^= 0x7f;
	}
	t01 = amtor_check_table[(pk01 &= 0x7f)];
	t02 = amtor_check_table[(pk02 &= 0x7f)];
	t03 = amtor_check_table[(pk03 &= 0x7f)];
	t11 = amtor_check_table[(pk11 &= 0x7f)];
	t12 = amtor_check_table[(pk12 &= 0x7f)];
	t13 = amtor_check_table[(pk13 &= 0x7f)];	
	if (t01 == ap.mycall[0] && t02 == AMTOR_RQ && t03 == ap.mycall[1] &&
	    t11 == ap.mycall[2] && t12 == ap.mycall[3] && t13 == AMTOR_RQ) {
		dev = soft_time_dev(samples-20*STANDBY_OVERSAMPLING_100, 21, STANDBY_OVERSAMPLING_100, 
				    1000000/100/STANDBY_OVERSAMPLING_100)+
			soft_time_dev(samples-20*STANDBY_OVERSAMPLING_100-450*STANDBY_OVERSAMPLING_100/10, 21, 
				      STANDBY_OVERSAMPLING_100, 1000000/100/STANDBY_OVERSAMPLING_100);
		dev /= 2;
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: call %c%c%c%c  dT=%ld\n", 
			  amtor_chars[ap.mycall[0]], amtor_chars[ap.mycall[1]], 
			  amtor_chars[ap.mycall[2]], amtor_chars[ap.mycall[3]], dev);
		as.is_master = 0;
		as.rxtime = tm-20*1000000/100+dev;
		as.txtime = as.rxtime + 210000 + ap.txdelay + 10000;
		as.rxfreqdev = as.txfreqdev = freqdev;
		l1_fsk_clear_requests();
		arq_statemachine();
		return 1;
	}
	if (freqdev)
		return 0;
	if (amtor_check_table[0x7f ^ pk01] == ap.mycall[0] &&
	    amtor_check_table[0x7f ^ pk02] == AMTOR_RQ &&
	    amtor_check_table[0x7f ^ pk03] == ap.mycall[1] &&
	    amtor_check_table[0x7f ^ pk11] == ap.mycall[2] &&
	    amtor_check_table[0x7f ^ pk12] == ap.mycall[3] &&
	    amtor_check_table[0x7f ^ pk13] == AMTOR_RQ) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: inverted call %c%c%c%c\n", 
			  amtor_chars[ap.mycall[0]], amtor_chars[ap.mycall[1]], 
			  amtor_chars[ap.mycall[2]], amtor_chars[ap.mycall[3]]);
		return 0;
	}
	if (/*t01 < 0 || t02 < 0 || t03 < 0 ||*/ t11 < 0 || t12 < 0 || t13 < 0)
		return 0;
	if ((unsigned)(tm - as.mon_mute) > 9000)
		monitor_triple("STDBY: RX: ", t11, t12, t13);
	as.mon_mute = tm;
	dev = soft_time_dev(samples-20*STANDBY_OVERSAMPLING_100, 21, STANDBY_OVERSAMPLING_100, 
			    1000000/100/STANDBY_OVERSAMPLING_100);
	tm += dev;
	for (i = k = 0, j = 10; i < MONNUM; i++) {
		if (j >= as.mon[i].state) {
			j = as.mon[i].state;
			k = i;
		}
		if (as.mon[i].state < 0)
			continue;
		if ((signed)(tm - as.mon[i].tm) > 15*450000) {
			j = -1;
			k = i;
			as.mon[i].state = -1;
			continue;
		}
		while ((signed)(tm - as.mon[i].tm) > 100000 && as.mon[i].state >= 0) {
			as.mon[i].tm += 450000;
			as.mon[i].state--;
		}
		if (j >= as.mon[i].state) {
			j = as.mon[i].state;
			k = i;
		}
		if (as.mon[i].state < 0)
			continue;
		if (labs((signed)(tm + 450000 - as.mon[i].tm)) < 5000)
			return 0;
		if (labs((signed)(tm - as.mon[i].tm)) < 5000) {
			if (as.mon[i].state < 8)
				as.mon[i].state++;
			if (as.mon[i].state > 3 && (t11 < 32 || t11 == AMTOR_BETA) && 
			    (t12 < 32 || t12 == AMTOR_BETA) && (t13 < 32 || t13 == AMTOR_BETA))
				output_triple(t11, t12, t13);
			as.mon[i].tm = tm + 450000;
			/*
			bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: STANDBY: pos %d state %d time %08lu\n", 
			*/
			/* i (guenther) took this out because monitor 
			makes my slow machine crash with this output */
			bufprintf(HFAPP_MSG_DATA_MONITOR, ".");
			return 0;
		}
	}
	as.mon[k].tm = tm + 450000;
	as.mon[k].state = 1;
	for (i = 0; i < MONNUM; i++)
		if (as.mon[i].state >= 0)
			bufprintf(HFAPP_MSG_DATA_MONITOR, 
			    /* "AMTOR: STANDBY: pos %d state %d time %08lu\n", 
			      i, as.mon[i].state, as.mon[i].tm); */
			      ".");
	return 0;
}

/* --------------------------------------------------------------------- */

int amtor_monitor_fec(l1_time_t tm, unsigned char rx0, unsigned char dx0,
		      unsigned char rx1, unsigned char dx1,
		      unsigned char rx2, unsigned char dx2,
		      unsigned char rx3, unsigned char dx3,
		      unsigned char rx4, unsigned char dx4,
		      unsigned char rx5, unsigned char dx5, l1_soft_t *samples, int freqdev)
{
	unsigned char ps1 = amtor_encode_table[AMTOR_PS1];
	unsigned char ps2 = amtor_encode_table[AMTOR_PS2];
	unsigned char invps1 = ps1 ^ 0x7f;
	unsigned char invps2 = ps2 ^ 0x7f;

	if (ap.rxinvert) {
		rx0 ^= 0x7f;
		dx0 ^= 0x7f;
		rx1 ^= 0x7f;
		dx1 ^= 0x7f;
		rx2 ^= 0x7f;
		dx2 ^= 0x7f;
		rx3 ^= 0x7f;
		dx3 ^= 0x7f;
		rx4 ^= 0x7f;
		dx4 ^= 0x7f;
		rx5 ^= 0x7f;
		dx5 ^= 0x7f;
	}
	rx0 &= 0x7f;
	dx0 &= 0x7f;
	rx1 &= 0x7f;
	dx1 &= 0x7f;
	rx2 &= 0x7f;
	dx2 &= 0x7f;
	rx3 &= 0x7f;
	dx3 &= 0x7f;
	rx4 &= 0x7f;
	dx4 &= 0x7f;
	rx5 &= 0x7f;
	dx5 &= 0x7f;
	if (rx0 == ps1 && rx1 == ps1 && rx2 == ps1 && rx3 == ps1 &&
	    dx2 == ps2 && dx3 == ps2 && dx4 == ps2 && dx5 == ps2) {
		as.last_ch = (dx1 << 8) | dx0;
		as.rxtime = tm + 1000000/100 + 
			soft_time_dev(samples - (12 * 7 - 1) * STANDBY_OVERSAMPLING_100, 12*7, STANDBY_OVERSAMPLING_100,
				      1000000/100/STANDBY_OVERSAMPLING_100);
		as.is_master = 0;
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FEC phasing\n");
		as.rxfreqdev = as.txfreqdev = freqdev;
		fec_rx();
		return 1;
	}
	if (freqdev == 0 && rx0 == invps1 && rx1 == invps1 && rx2 == invps1 && rx3 == invps1 &&
	    dx2 == invps2 && dx3 == invps2 && dx4 == invps2 && dx5 == invps2) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: inverted FEC phasing\n");
		return 0;
	}
	return 0;
}

/* --------------------------------------------------------------------- */

static void fec_tx(int sel, const char *mode)
{
	int inv = ap.txinvert;
	unsigned long id;
	int cnt, i;
	unsigned int bits = amtor_encode_table[AMTOR_PS2] | (amtor_encode_table[AMTOR_PS1] << 7);
	unsigned char *bp;
	unsigned short txch;

	l1_fsk_clear_requests();
	kbd_clear_and_fill(NULL, 0);
	as.flags = 0;
	as.figmode = 0;
	as.lcflag = 0;
	as.txbuf[0] = as.txbuf[2] = as.txbuf[4] = as.txbuf[6] = bits & 0xff;
	as.txbuf[1] = as.txbuf[3] = as.txbuf[5] = as.txbuf[7] = bits >> 8;
	as.txtime = l1_get_current_time() + 100000;
	as.rxfreqdev = as.txfreqdev = 0;
	as.last_ch = amtor_encode_table[AMTOR_PS1];
	as.last_ch |= (as.last_ch << 8);
	for (id = 0; id < 4; id++) {
		l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, inv, id, 14, as.txbuf+2*id);
		as.txtime += 140000;
	}
	bufprintf(HFAPP_MSG_DATA_STATUS, "%s\nSYNC", mode);
	send_short_msg(HFAPP_MSG_STATE_AMTOR_FEC_CONNECT, ERR_NOERR);
	for (cnt = 0; cnt < 10; cnt++) {
		id = l1_fsk_wait_request();
		assert(id < 4);
		l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, inv, id, 14, as.txbuf+2*id);
		as.txtime += 140000;
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FEXTX: PS1/PS2\n");
	}
	if (sel) {
		bufprintf(HFAPP_MSG_DATA_STATUS, "%s\nSELFEC CALL\n", mode);
		inv = !inv;
		for (cnt = 0; cnt < 5; cnt++) {
			for (i = 0; i < 5; i++) {				
				id = l1_fsk_wait_request();
				assert(id < 4);
				bp = as.txbuf+2*id;
				txch = (i < 4) ? ap.selfeccall[i] : AMTOR_BETA;
				as.last_ch = (as.last_ch << 8) | amtor_encode_table[txch];
				bits = (as.last_ch & 0x7f) | ((as.last_ch >> 9) & 0x3f80);
				bp[0] = bits & 0xff;
				bp[1] = bits >> 8;
				l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, inv, id, 14, bp);
				as.txtime += 140000;
				bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FEXTX: %s\n", (i < 4) ? monitor_names[txch] : "BETA");
			}
		}
	}
	bufprintf(HFAPP_MSG_DATA_STATUS, "%s\nDATA\n", mode);
	while (!(as.flags & FLG_QRT)) {
		id = l1_fsk_wait_request();
		assert(id < 4);
		bp = as.txbuf+2*id;
		as.last_ch <<= 8;
		txch = get_tx_char();
		if (txch == AMTOR_BETA && !sel) {
			as.last_ch |= amtor_encode_table[AMTOR_PS1];
			bits = amtor_encode_table[AMTOR_PS2];
			bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FEXTX: PS1/PS2\n");
		} else {
			bits = amtor_encode_table[txch];
			as.last_ch |= bits;
			bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FEXTX: %s\n", monitor_names[txch]);
		}
		bits |= ((as.last_ch >> 9) & 0x3f80);
		bp[0] = bits & 0xff;
		bp[1] = bits >> 8;
		l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, inv, id, 14, bp);
		as.txtime += 140000;
	}
	bufprintf(HFAPP_MSG_DATA_STATUS, "%s\nQRT\n", mode);
	for (i = 0; i < 2; i++) {
		id = l1_fsk_wait_request();
		assert(id < 4);
		bp = as.txbuf+2*id;
		as.last_ch = (as.last_ch << 8) | amtor_encode_table[AMTOR_ALPHA];
		bits = (as.last_ch & 0x7f) | ((as.last_ch >> 9) & 0x3f80);
		bp[0] = bits & 0xff;
		bp[1] = bits >> 8;
		l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, inv, id, 14, bp);
		as.txtime += 140000;
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: FEXTX: ALPHA\n");
	}
	for (i = 0; i < 4; i++) {
		id = l1_fsk_wait_request();
		assert(id < 4);
	}
	send_short_msg(HFAPP_MSG_STATE_AMTOR_FEC_DISCONNECT, ERR_NOERR);
	return;
}

/* --------------------------------------------------------------------- */

static int master_verify_cs(int callflg)
{
	unsigned char csbuf;
	unsigned char cs1 = amtor_encode_table[AMTOR_CS1];
	unsigned int bits;
	long dev;

	if (callflg)
		bits = amtor_encode_table[ap.destcall[2]] |
			(amtor_encode_table[ap.destcall[3]] << 7) |
			(amtor_encode_table[AMTOR_RQ] << 14);
	else
		bits = amtor_encode_table[ap.destcall[0]] |
			(amtor_encode_table[AMTOR_RQ] << 7) |
			(amtor_encode_table[ap.destcall[1]] << 14);
	as.txbuf[0] = bits & 0xff;
	as.txbuf[1] = (bits >> 8) & 0xff;
	as.txbuf[2] = (bits >> 16) & 0xff;
	l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, ap.txinvert, 0x101, 21, as.txbuf);
	l1_fsk_tx_request(as.txtime+450000-ap.txdelay, ap.txdelay, as.txfreqdev, 0, 0x100, 1, as.txbuf);
	l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100, 0x200,
			  7*RXOVERSAMPLING, as.rxbuf);
	if (callflg)
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: %s_%s_%s\n", 
			  monitor_names[ap.destcall[2]], monitor_names[ap.destcall[3]], monitor_names[AMTOR_RQ]);
	else
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: %s_%s_%s\n", 
			  monitor_names[ap.destcall[0]], monitor_names[AMTOR_RQ], monitor_names[ap.destcall[1]]);
	while (l1_fsk_wait_request() != 0x200);
	soft_to_hard(as.rxbuf, &csbuf, 7, RXOVERSAMPLING, ap.rxinvert);
	dev = soft_time_dev(as.rxbuf, 7, RXOVERSAMPLING, 1000000/100/RXOVERSAMPLING);
	if ((csbuf & 0x7f) == cs1) {
		bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: CS1  dT=%ld\n", dev);
		return 1;
	}
	bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: CS?  bits=%02x\n", csbuf & 0x7f);
	return 0;
}

/* --------------------------------------------------------------------- */

static int master_call(void)
{
	unsigned int nsamps = (450000-210000-ap.txdelay)/(1000000/100)*RXOVERSAMPLING;
	int i;
	l1_soft_t *s;
	unsigned char csbuf;
	unsigned char cs1 = amtor_encode_table[AMTOR_CS1];
	unsigned int bits;
	int callflg = 0;

	as.txtime = l1_get_current_time() + 200000;
	as.rxfreqdev = as.txfreqdev = 0;
	as.retry = ap.retry;
	as.is_master = 1;
	l1_fsk_tx_request(as.txtime-ap.txdelay, ap.txdelay, as.txfreqdev, 0, 0x100, 1, as.txbuf);
	for (;;) {
		if (callflg)
			bits = amtor_encode_table[ap.destcall[2]] |
				(amtor_encode_table[ap.destcall[3]] << 7) |
				(amtor_encode_table[AMTOR_RQ] << 14);
		else
			bits = amtor_encode_table[ap.destcall[0]] |
				(amtor_encode_table[AMTOR_RQ] << 7) |
				(amtor_encode_table[ap.destcall[1]] << 14);
		as.txbuf[0] = bits & 0xff;
		as.txbuf[1] = (bits >> 8) & 0xff;
		as.txbuf[2] = (bits >> 16) & 0xff;
		l1_fsk_tx_request(as.txtime, 1000000/100, as.txfreqdev, ap.txinvert, 0x101, 21, as.txbuf);
		l1_fsk_tx_request(as.txtime+450000-ap.txdelay, ap.txdelay, as.txfreqdev, 0, 0x100, 1, as.txbuf);
		as.rxtime = as.txtime+210000;
		l1_fsk_rx_request(as.rxtime, 1000000/100/RXOVERSAMPLING, as.rxfreqdev, 100, 0x200,
				  nsamps, as.rxbuf);
		if (callflg)
			bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: %s_%s_%s\n", 
				  monitor_names[ap.destcall[2]], monitor_names[ap.destcall[3]], monitor_names[AMTOR_RQ]);
		else
			bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: TX: %s_%s_%s\n", 
				  monitor_names[ap.destcall[0]], monitor_names[AMTOR_RQ], monitor_names[ap.destcall[1]]);
		disp_status("ARQ MASTER CALL");
		while (l1_fsk_wait_request() != 0x200);
		for (s = as.rxbuf, i = 0; i < nsamps-RXOVERSAMPLING*(7-1); 
		     i++, s++, as.rxtime += 1000000/100/RXOVERSAMPLING) {
			soft_to_hard(s, &csbuf, 7, RXOVERSAMPLING, ap.rxinvert);
			if ((csbuf & 0x7f) == cs1) {
				as.rxtime += 450000 + soft_time_dev(s, 7, RXOVERSAMPLING, 1000000/100/RXOVERSAMPLING);
				as.txtime += 450000;
				bufprintf(HFAPP_MSG_DATA_MONITOR, "AMTOR: RX: CS1  dT=%ld\n", as.rxtime-as.txtime-210000);
				callflg = !callflg;
				if (master_verify_cs(callflg)) {
					as.txtime += 450000;
					return 1;
				}
			}
		}
		if ((--as.retry) <= 0)
			return -1;
		as.txtime += 450000;
		callflg = !callflg;
	}
}

/* --------------------------------------------------------------------- */

void *mode_amtor_arq(void *dummy)
{
	int i;

	l1_fsk_clear_requests();
	errprintf(SEV_INFO, "mode: amtor arq call\n");
	i = master_call();
	if (i >= 0)
		arq_statemachine();
	else
	    send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_MASTERCONNECT, ERR_TIMEOUT);
	    send_short_msg(HFAPP_MSG_STATE_AMTOR_ARQ_DISCONNECT, ERR_TIMEOUT);
	return NULL;
}

void *mode_amtor_colfec(void *dummy)
{
	errprintf(SEV_INFO, "mode: amtor colfec\n");
	fec_tx(0, "AMTOR COLLECTIVE FEC TRANSMIT");
	return NULL;
}

void *mode_amtor_selfec(void *dummy)
{
	errprintf(SEV_INFO, "mode: amtor selfec\n");
	fec_tx(1, "AMTOR SELECTIVE FEC TRANSMIT");
	return NULL;
}

/* --------------------------------------------------------------------- */

PSK31

Peter Martinez, G3PLX, who was instrumental in bringing us AMTOR, also developed PSK31 for real time keyboard-tokeyboard QSOs. This section was adapted from an article in RadCom, Jan 1999. The name derives from the modulation type (phase-shift keying) and the data rate, which is actually 31.25 bauds. PSK31 is a robust mode for HF communications that features the 128 ASCII (Internet) characters and the full 256 ANSI character set. This mode works well for two-way QSOs and for nets. Time will tell if PSK31 will replace Baudot RTTY on the amateur HF bands.

Morse code uses a single carrier frequency keyed on and off as dits and dahs to form characters. RTTY code shifts between two frequencies, one for mark (1) the other for space (0). Sequences of marks and spaces comprise the various characters.

Martinez devised a new variable-length code for PSK31 that combines the best of Morse and RTTY. He calls it Varicode because a varying number of bits are used for each character (see Fig 9.13). Much like Morse code, the more commonly used letters in PSK31 have shorter codes.

As with RTTY, there is a need to signal the gaps between characters. The Varicode does this by using “00” to represent a gap.

The Varicode is structured so that two zeros never appear together in any of the combinations of 1s and 0s that make up the characters.

In on-the-air tests, Martinez has verified that the unique “00” sequence works significantly better than RTTY’s stop code for keeping the receiver synchronized.

With Varicode, a typing speed of about 50 words per minute requires a 32 bit/s transmission rate. Martinez chose 31.25 bit/s because it can be easily derived from the 8-kHz sample rate used in many DSP systems.

The shifting carrier phase generates sidebands 31.25 Hz from the carrier. These are used to  synchronize the receiver with the transmitter. The bandwidth of a PSK31 signal is shown in Fig 9.14.

PSK31 Error Correction

Martinez added error correction to PSK31 by using QPSK (quaternary phase shift keying) and a convolutional encoder to generate one of four different phase shifts that correspond to patterns of five successive data bits. At the receiving end, a Viterbi decoder is used to correct errors.

There are 32 possible sequences for five bits. The Viterbi decoder tracks these possibilities while discarding the least likely and retaining the most likely sequences. Retained sequences are given a score that is based on the running total. The most accurate sequence is reported, and thus errors are corrected.

Operating PSK31 in the QPSK mode should result in 100% copy under most conditions, but at a price. Tuning is twice as critical as it is with BPSK. An accuracy of less than 4 Hz is required for the Viterbi decoder to function properly.

Getting Started

In addition to a transceiver and antenna, you only need a computer with a Windows operating system and a 16-bit sound card to receive and transmit PSK31. Additional information and software is available for free download over the Web. Use a search engine to find PSK31 information and links to downloads.

An interesting wrinkle is to generate text, transmit it via PSK31 or some other RTTY or data mode, receive it and use a speech synthesizer to read the message. An example of this technique was described by W3NRG in the October 2004 issue of CQ Magazine (p 48). Synthesized speech takes some getting used to, as everybody sounds pretty much alike, and the personality of the speaker does not come through.

PSK31 BIBLIOGRAPHY

Ford, Steve, WB8IMY, ARRL’s HF Digital

Handbook, Third Ed., ARRL, 2004.

C++ Code help & descriptions will be available soon.

 

 

 

PACTOR

PACTOR (PT), now often referred to as PACTOR-I, is an HF radio transmission system developed by German amateurs Hans-Peter Helfert, DL6MAA, and Ulrich Strate, DF4KV. It was designed to overcome the shortcomings of AMTOR and packet radio.

It performs well under both weak-signal and high-noise conditions. PACTOR-I has been overtaken by PACTOR-II and PACTOR-III but remains in use.

TRANSMISSION FORMATS

Information Blocks

All packets have the basic structure shown in Fig 9.19, and their timing is as shown in Table 9.1:

Header: Contains a fixed bit pattern to simplify repeat requests, synchronization and monitoring. The header is also important for the Memory ARQ function. In each packet carrying new information, the bit pattern is inverted.

Data: Any binary information. The format is specified in the status word. Current choices are 8-bit ASCII or 7-bit ASCII (with Huffman encoding). Characters are not broken across packets. ASCII RS (hex 1E) is used as an IDLE character in both formats.

Status word: See Table 9.2

CRC: The CRC is calculated according to the CCITT standard, for the data, status and CRC.

Table 9.1 PACTOR Timing

Object Length (seconds)
Packet 0.96 (200 bd:192 bits; 100 bd:96 bits)
CS receive time 0.29
Control signals 0.12 (12 bits at 10 ms each)
Propagation delay 0.17
Cycle 1.25

Table 9.2 PACTOR Status Word

Bit Meaning
0 Packet count (LSB)
1 Packet count (MSB)
2 Data format (LSB)
3 Data format (MSB)
4 Not defined
5 Not defined
6 Break-in request
7 QRT request

Table 9.3 Data Format Bits

Format bit 3 bit 2
ASCII 8 bit 0 0
Huffman code 0 1
Not defined 1 0
Not defined 1 1
Bits 0 and 1 are used as a packet count;successive packets with the same value are identified by the receiver as repeat packets.A modulus-4 count helps with unrecognized control signals, which are unlikely in practice.

Acknowledgment Signals

The PACTOR acknowledgment signals are shown in Table 9.3. Each of the signals is 12 bits long. The characters differ in pairs in 8 bits (Hamming offset) so that the chance of confusion is reduced. If the CS is not correctly received, the TX reacts by repeating the last packet. The request status can be uniquely recognized by the 2-bit packet number so that wasteful transmissions of pure RQ blocks are unnecessary.

Timing

The receiver pause between two blocks is 0.29 s. After deducting the CS lengths, 0.17 s remain for switching and propagation delays so that there is adequate reserve for DX operation.

CONTACT FLOW

Listening

In the listen mode, the receiver scans any received packets for a CRC match. This method uses a lot of computer processing resources, but it’s flexible.

CQ

A station seeking contacts transmits CQ packets in an FEC mode, without pauses for acknowledgment between packets. The transmit time length, number of repetitions and speed are the transmit operator’s choice.

(This mode is also suitable for bulletins and other group traffic.) Once a listening station has copied the call, the listener assumes the TX station role and initiates a contact. Thus, the station sending CQ initially takes the RX station role. The contact begins as shown in Table 9.4

Speed Changes

With good conditions, PACTOR’s normal signaling rate is 200 bauds, but the system automatically changes from 200 to 100 bauds and back, as conditions demand.

In addition, Huffman coding can further increase the throughput by a factor of 1.7.

There is no loss of synchronization speed changes; only one packet is repeated.

When the RX receives a bad 200-baud packet, it can acknowledge with CS4. TX immediately assembles the previous packet in 100-baud format and sends it. Thus, one packet is repeated in a change from 200 to 100 bauds.

The RX can acknowledge a good 100-baud packet with CS4. TX immediately switches to 200 bauds and sends the next packet. There is no packet repeat in an upward speed change.

Change of Direction

The RX station can become the TX station by sending a special change-over packet in response to a valid packet. RX sends CS3 as the first section of the changeover packet. This immediately changes the TX station to RX mode to read the data in that packet and responds with CS1 and CS3 (acknowledge) or CS2 (reject).

End of Contact

PACTOR provides a sure end-of-contact procedure. TX initiates the end of contact by sending a special packet with the QRT bit set in the status word and the call of the RX station in byte-reverse order at 100 bauds. The RX station responds with a final CS.

PACTOR-II

This is a significant improvement over PACTOR-I, yet it is fully compatible with the older mode. Also invented in Germany, PACTOR uses 16PSK to transfer up to 800 bit/s at a 100-baud rate. This keeps the bandwidth less than 500 Hz.

PACTOR-II uses digital signal processing (DSP) with Nyquist waveforms, Huffman and Markov compression, and powerful Viterbi decoding to increase transfer rate and sensitivity into the noise level.

The effective transfer rate of text is over 1200 bit/s. Features of PACTOR II include:

• Frequency agility—It can automatically adjust or lock two signals together over a ±100-Hz window.

• Powerful data reconstruction based upon computer power—With over 2 MB of available memory.

• Cross correlation—Applies analog Memory ARQ to acknowledgment frames and headers.

• Soft decision making—Uses artificial intelligence (AI), as well as digital information received to determine frame validity.

• Extended data block length—When transferring large files under good conditions, the data length is doubled to increase the transfer rate.

• Automatic recognition of PACTOR-I, PACTOR-II and so on, with automatic mode switching.

• Intermodulation products are canceled by the coding system.

• Two long-path modes extend frame timing for long-path terrestrial and satellite propagation paths.

This is a fast, robust mode, possibly the most powerful in the ham bands. It has excellent coding gain as well. It can also communicate with all earlier PACTOR-I systems. PACTOR-II stations acknowledge each received transmission block.

PACTOR-II employs computer logic as well as received data to reassemble defective data blocks into good frames. This reduces the number of transmissions and increases the throughput of the data.

Table 9.4 PACTOR Control Signals

Code Chars (hex) Function
CS1 4D5 Normal acknowledge
CS2 AB2 Normal acknowledge
CS3 34B Break-in (forms header of first packet from RX to TX)
CS4 D2C Speed change request All control signals are sent only from RX to TX.

PACTOR-III

PACTOR-III is a software upgrade for existing PACTOR-II modems that provides a data transmission mode for improved speed and robustness. PACTOR-III is not a new modem or hardware device. Most current PACTOR-II modems are upgradeable to use PACTOR-III via a software update, since PACTOR-II firmware accommodates the new PACTOR-III software. Both the transmitting and receiving stations must support PACTOR-III for end-to-end communications using this mode.

PACTOR-III’s maximum uncompressed speed is 2722 bit/s. Using online compression, up to 5.2 kbit/s is achievable. This requires an audio passband from 400 Hz to 2600 Hz (for PACTOR-III speed level 6). On an average channel, PACTOR-III is more than three times faster than PACTORII.

On good channels, the effective throughput ratio between PACTOR-III and PACTOR-II can exceed five. PACTOR-III is also slightly more robust than PACTORII at their lower SNR edges.

The ITU emission designator for PACTOR-III is 2K20J2D. Because PACTOR-III builds on PACTOR-II, most specifications like frame length and frame structure are adopted from PACTOR-II. The only significant difference is PACTOR III’s multi-tone waveform that uses up to 18 carriers while PACTORII uses only two carriers. PACTOR-III’s carriers are located in a 120-Hz grid and modulated with 100 symbols per second DBPSK or DQPSK. Channel coding is also adopted from PACTOR-II’s Punctured Convolutional Coding.

PACTOR-III Link Establishment

The calling modem uses the PACTOR-I FSK connect frame for compatibility. When the called modem answers, the modems negotiate to the highest level of which both modems are capable. If one modem is only capable of PACTOR-II, then the 500 Hz PACTOR-II mode is used for the session.

With the MYLevel (MYL) command a user may limit a modem’s highest mode. For example, a user may set MYL to “1” and only a PACTOR-I connection will be made, set to “2” and PACTOR-I and II connections are available, set to “3” and PACTOR-I through III connections are enabled. The default MYL is set to “2” with the current firmware and with PACTOR-III firmware it will be set to “3”. If a user is only allowed to occupy a 500 Hz channel, MYL can be set to “2” and the modem will stay in its PACTOR-II mode. The PACTOR-III Protocol

Specification is available on the Web at www.scs-ptc.com/pactor.html.

Table 9.5 PACTOR Initial Contact – Master Initiating Contact

Size (bytes) 1 8 6
Content /Header /SLAVECAL /SLAVECAL/
Speed (bauds) 100 100 200

Slave Response

The receiving station detects a call, determines mark/space polarity, decodes 100-bd and 200-bd call signs. It uses the two call signs to determine if it is being called and the quality of the communication path. The possible responses are:

First call sign does not match slave’s (Master not calling this slave)                                       none

Only first call sign matches slave’s (Master calling this slave, poor communications)            CS1

First and second call signs both match the slaves (good circuit, request speed change          CS4

to 200 bd)

PACTOR Bibliography

ARRL Web, Technical Descriptions, www.arrl.org/FandES/field/regulations/techchar/.

Ford, Steve, WB8IMY, ARRL’s HF Digital Handbook, Third Ed., ARRL. 2004.

C++ Code help & descriptions will be available soon.

G-TOR

This brief description has been adapted from “A Hybrid ARQ Protocol for Narrow Bandwidth HF Data Communication” by Glenn Prescott, WBØSKX, Phil Anderson, WØXI, Mike Huslig, KBØNYK, and Karl Medcalf, WK5M (May 1994 QEX).

G-TOR is short for Golay-TOR, an innovation of Kantronics, Inc. It was inspired by HF Automatic Link Establishment (ALE) concepts and is structured to be compatible with ALE.

The purpose of the G-TOR protocol is to provide an improved digital radio communication capability for the HF bands. The key features of G-TOR are:

Standard FSK tone pairs (mark and space)

  • Link-quality-based signaling rate: 300, 200 or 100 bauds
  • 2.4-second transmission cycle
  • Low overhead within data frames
  • Huffman data compression—two types, on demand
  • Embedded run-length data compression
  • Golay forward-error-correction coding
  • Full-frame data interleaving
  • CRC error detection with hybrid ARQ
  • Error-tolerant “Fuzzy” acknowledgments.

The G-TOR Protocol

Since one of the objectives of this protocol is ease of implementation in existing TNCs, the modulation format consists of standard tone pairs (FSK), operating at 300, 200 or 100 bauds, depending upon channel conditions. (G-TOR initiates contacts and sends ACKs only at 100 bauds.) The G-TOR waveform consists of two phase-continuous tones (BFSK), spaced 200 Hz apart (mark = 1600 Hz, space = 1800 Hz); however, the system can still operate at the familiar 170- Hz shift (mark = 2125 Hz, space = 2295 Hz), or with any other convenient tone pairs. The optimum spacing for 300-baud transmission is 300 Hz, but you trade some performance for a narrower bandwidth.

Each transmission consists of a synchronous ARQ 1.92-s frame and a 0.48-s interval for propagation and ACK transmissions (2.4 s cycle). All advanced protocol features are implemented in the signal-processing software.

Data Compression

Data compression is used to remove redundancy from source data. Therefore, fewer bits are needed to convey any given message. This increases data throughput and decreases transmission time—valuable features for HF. G-TOR uses run-length coding and two types of Huffman coding during normal text transmissions. Run-length coding is used when more than two repetitions of an 8-bit character are sent. It provides an especially large savings in total transmission time when repeated characters are being transferred.

The Huffman code works best when the statistics of the data are known. G-TOR applies Huffman A coding with the upper- and lower-case character set, and Huffman B coding with upper-case-only text. Either type of Huffman code reduces the average number of bits sent per character. In some

situations, however, there is no benefit from Huffman coding. The encoding process is then disabled. This decision is made on a frame-by-frame basis by the informationsending station.

Golay Coding

The real power of G-TOR resides in the properties of the (24,12) extended Golay error-correcting code, which permits correction of up to three random errors in three received bytes. The (24,12) extended Golay code is a half-rate error-correcting code: Each 12 data bits are translated into an additional 12 parity bits (24 bits total).

Further, the code can be implemented to produce separate input-data and parity-bit frames.

The extended Golay code is used for G-TOR because the encoder and decoder are simple to implement in software. Also, Golay code has mathematical properties that make it an ideal choice for short-cycle synchronous communication.

G-TOR Bibliography

ARRL Web, Technical Descriptions, www.arrl.org/FandES/field/regula-Table 9.4

C++ Code help & descriptions will be available soon.

Glossary of Digital Communications Terminology

ACK—Acknowledgment, the control signal sent to indicate the correct receipt of a transmission block.

Address—A character or group of characters that identifies a source or destination.

AFSK—Audio frequency-shift keying.

ALE—Automatic link establishment.

AMRAD—Amateur Radio Research and Development Corporation, a nonprofit organization dedicated to experimentation.

AMSAT—Radio Amateur Satellite Corporation.

AMTOR—Amateur teleprinting over radio, an amateur radioteletype transmission technique employing error correction as specified in several ITU-R Recommendations M.476-2 through M.476-4 and M.625.

ANSI—American National Standards Institute.

Answer—The station intended to receive a call. In modem usage, the called station or modem tones associated therewith.

APCO—Association of Public Safety Communications Officials.

ARQ—Automatic Repeat reQuest, an error-sending station, after transmitting a data block, awaits a reply (ACK or NAK) to determine whether to repeat the last block or proceed to the next.

ASCII—American National Standard Code for Information Interchange, a code consisting of seven information bits.

AX.25—Amateur packet-radio link-layer protocol. Copies of protocol specification are available from ARRL HQ.

Backwave—An unwanted signal emitted between the pulses of an on/off-keyed signal.

Balanced—A relationship in which two stations communicate with one another as equals; that is, neither is a primary (master) or secondary (slave).

Baud—A unit of signaling speed equal to the number of discrete conditions or events per second. (If the duration of a pulse is 20 ms, the signaling rate is 50 bauds or the reciprocal of 0.02, abbreviated Bd).

Baudot code—A coded character set in which five bits represent one character. Used in the US to refer to ITA2.

Bell 103—A 300-baud full-duplex modem using 200-Hz-shift FSK of tones centered at 1170 and 2125 Hz.

Bell 202—A 1200-baud modem standard with 1200-Hz mark, 2200-Hz space, used for VHF FM packet radio.

BER—Bit error rate.

BERT—Bit-error-rate test.

Bit stuffing—Insertion and deletion of 0s in a frame to preclude accidental occurrences of flags other than at the beginning and end of frames.

Bit—Binary digit, a single symbol, in binary terms either a one or zero.

Bit/s—Bits per second.

BLER—Block error rate.

BLERT—Block-error-rate test.

Break-in—The ability to hear between elements or words of a keyed signal.

Byte—A group of bits, usually eight.

Carrier detect (CD)—Formally, received line signal detector, a physical-level interface signal that indicates that the receiver section of the modem is receiving tones from the distant modem.

CDMA—Code division multiple access.

Chirp—Incidental frequency modulation of a carrier as a result of oscillator instability during keying.

CLOVER—Trade name of digital communications system developed by Hal Communications.

COFDM—Coded Orthogonal Frequency Division Multiplex,

OFDM plus coding to provide error correction and noise immunity.

Collision—A condition that occurs when two or more transmissions occur at the same time and cause interference to the intended receivers.

Constellation—A set of points in the complex plane which represent the various combinations of phase and amplitude in a QAM or other complex modulation scheme.

Contention—A condition on a communications channel that occurs when two or more stations try to transmit at the same time.

Control field—An 8-bit pattern in an HDLC frame containing commands or responses, and sequence numbers.

CRC—Cyclic redundancy check, a mathematical operation. The result of the CRC is sent with a transmission block. The receiving station uses the received CRC to check transmitted data integrity.

CSMA—Carrier sense multiple access, a channel access arbitration scheme in which packet-radio stations listen on a channel for the presence of a carrier before transmitting a frame.

CTS—clear to send, a physical-level interface circuit generated by the DCE that, when on, indicates the DCE is ready to receive transmitted data (abbreviated CTS).

DARPA—Defense Advanced Research Projects Agency.

DBPSK—Differential binary phase-shift keying.

DQPSK—Differential quadrature phase-shift keying.

DCE—Data circuit-terminating equipment, the equipment (for example, a modem) that provides communication between the DTE and the line radio equipment.

Domino—A conversational HF digital mode similar in some respects to MFSK16.

DRM—Digital Radio Mondiale. A consortium of broadcasters, manufacturers, research and governmental organizations which developed a system for digital sound broadcasting in bands between 100 kHz and 30 MHz.

EIA—Electronic Industries Alliance.

EIA-232—An EIA standard physical-level interface between DTE (terminal) and DCE (modem), using 25-pin connectors. Formerly RS-232, a popular serial line standard, equivalent of ITU-T V.24 and V.28.

Envelope-delay distortion—In a complex waveform, unequal propagation delay for different frequency components.

Equalization—Correction for amplitude-frequency and/or phase-frequency distortion.

Eye pattern—An oscilloscope display in the shape of one or more eyes for observing the shape of a serial digital stream and any impairments.

Facsimile (fax)—A form of telegraphy for the transmission of fixed images, with or without half-tones, with a view to their reproduction in a permanent form.

FCS—Frame check sequence. (See CRC.)

FDM—Frequency division multiplexing

FDMA—Frequency division multiple access

FEC—Forward error correction, an error-control technique in which the transmitted data is sufficiently redundant to permit the receiving station to correct some errors.

FSK—Frequency-shift keying.

GNU—A project to develop a free UNIX style operating system.

G-TOR—A digital communications system developed by Kantronics.

HDLC—High-level data link control procedures as specified in ISO 3309.

Hellschreiber—A facsimile system for transmitting text.

Host—As used in packet radio, a computer with applications programs accessible by remote stations.

IA5—International Alphabet No. 5, a 7-bit coded character set, ITU-T version of ASCII.

IBOC—In Band On Channel. A method of using the same channel on the AM or FM broadcast bands to transmit simultaneous digital and analog modulation.

Information field—Any sequence of bits containing the intelligence to be conveyed.

ISI—Intersymbol interference; slurring of one symbol into the next as a result of multipath propagation.

ISO—International Organization for Standardization.

ITA2—International Telegraph Alphabet No. 2, a ITU-T 5-bit coded character set commonly called the Baudot or Murray code.

ITU—International Telecommunication Union, a specialized

agency of the United Nations. (See www.itu.int.)

ITU-R—Radiocommunication Sector of the ITU, formerly CCIR.

ITU-T—Telecommunication Standardization Sector of the ITU, formerly CCITT.

Jitter—Unwanted variations in amplitude or phase in a digital signal.

Key clicks—Unwanted transients beyond the necessary bandwidth of a keyed radio signal.

LAP—Link access procedure, ITU-T Recommendation X.25 unbalanced-mode communications.

LAPB—Link access procedure, balanced, ITU-T Recommendation X.25 balanced-mode communications.

Layer—In communications protocols, one of the strata or levels in a reference model.

Level 1—Physical layer of the OSI reference model.

Level 2—Link layer of the OSI reference model.

Level 3—Network layer of the OSI reference model.

Level 4—Transport layer of the OSI reference model.

Level 5—Session layer of the OSI reference model.

Level 6—Presentation layer of the OSI reference model.

Level 7—Application layer of the OSI reference model.

Linux—A free Unix-type operating system originated by Linus Torvalds, et al. Developed under the GNU General Public License.

Loopback—A test performed by connecting the output of a modulator to the input of a demodulator.

LSB—Least-significant bit.

MFSK16—A multi-frequency shift communications system

Modem—Modulator-demodulator, a device that connects between a data terminal and communication line (or radio). Also called data set.

MSB—Most-significant bit.

MSK—Frequency-shift keying where the shift in Hz is equal to half the signaling rate in bits per second.

MT-63—A keyboard-to-keyboard mode similar to PSK31 and RTTY.

NAK—Negative acknowledge (opposite of ACK).

Node—A point within a network, usually where two or more links come together, performing switching, routine and concentrating functions.

NRZI—Nonreturn to zero. A binary baseband code in which output transitions result from data 0s but not from 1s. Formal designation is NRZ-S (nonreturn-to-zero-space).

Null modem—A device to interconnect two devices both wired as DCEs or DTEs; in EIA-232 interfacing, back-toback DB25 connectors with pin-for-pin connections except that Received Data (pin 3) on one connector is wired to Transmitted Data (pin 3) on the other.

Octet—A group of eight bits.

OFDM—Orthogonal Frequency Division Multiplex. A method of using spaced subcarriers that are phased in such a way as to reduce the interference between them.

Originate—The station initiating a call. In modem usage, the calling station or modem tones associated therewith.

OSI-RM—Open Systems Interconnection Reference Model specified in ISO 7498 and ITU-T Recommendation X.200.

Packet radio—A digital communications technique involving radio transmission of short bursts (frames) of data containing addressing, control and error-checking information in each transmission.

PACTOR®—Trade name of digital communications protocols offered by Special Communications Systems GmbH & Co KG (SCS).

Parity check—Addition of non-information bits to data, making the number of ones in a group of bits always either even or odd.

PID—Protocol identifier. Used in AX.25 to specify the network-layer protocol used.

Primary—The master station in a master-slave relationship; the master maintains control and is able to perform actions that the slave cannot. (Compare secondary.)

Project 25—Digital voice system developed for APCO, also known as P25.

Protocol—A formal set of rules and procedures for the exchange of information within a network.

PSK—Phase-shift keying.

PSK31—A narrow-band digital communications system developed by Peter Martinez, G3PLX.

Q15X25—A DSP-intensive mode intended as an error-free mode more reliable on HF than packet.

QAM—Quadrature Amplitude Modulation. A method of simultaneous phase and amplitude modulation. The number that precedes it, eg, 64QAM, indicates the number of discrete stages in each pulse.

QPSK—Quadrature phase-shift keying.

RAM—Random access memory.

Router—A network packet switch. In packet radio, a networklevel relay station capable of routing packets.

RTS—Request to send, physical-level signal used to control the direction of data transmission of the local DCE.

RTTY—Radioteletype.

RxD—Received data, physical-level signals generated by the DCE are sent to the DTE on this circuit.

SCAMP—Sound Card Automated Message Protocol, an inexpensive alternative to hardware for passing e-mail traffic on narrow-bandwidth channels.

Secondary—The slave in a master-slave relationship. Compare primary.

Source—In packet radio, the station transmitting the frame over a direct radio link or via a repeater.

SSID—Secondary station identifier. In AX.25 link-layer protocol, a multipurpose octet to identify several packetradio stations operating under the same call sign.

TAPR—Tucson Amateur Packet Radio Corporation, a nonprofit organization involved in packet-radio development.

TDM—Time division multiplexing

TDMA—Time division multiple access

Telecommand—The use of telecommunication for the transmission of signals to initiate, modify or terminate functions of equipment at a distance.

Telemetry—The use of telecommunication for automatically indicating or recording measurements at a distance from the measuring instrument.

Telephony—A form of telecommunication primarily intended for the exchange of information in the form of speech.

Telegraphy—A form of telecommunication in which the transmitted information is intended to be recorded on arrival as a graphic document; the transmitted information may sometimes be presented in an alternative form or may be stored for subsequent use.

Teleport—A radio station that acts as a relay between terrestrial radio stations and a communications satellite.

Television—A form of telecommunication for the transmission of transient images of fixed or moving objects.

Throb—A multi-frequency shift mode like MFSK16.

TNC—Terminal node controller, a device that assembles and disassembles packets (frames); sometimes called a PAD.

TR switch—Transmit-receive switch to allow automatic selection between receive and transmitter for one antenna.

TTY—Teletypewriter.

Turnaround time—The time required to reverse the direction of a half-duplex circuit, required by propagation, modem reversal and transmit-receive switching time of transceiver.

TxD—Transmitted data, physical-level data signals transferred on a circuit from the DTE to the DCE.

UI—Unnumbered information frame.

V.24—An ITU-T Recommendation defining physical-level interface circuits between a DTE (terminal) and DCE (modem), equivalent to EIA-232.

V.28—An ITU-T Recommendation defining electrical characteristics for V.24 interface.

Virtual circuit—A mode of packet networking in which a logical connection that emulates a point-to-point circuit is established (compare Datagram).

Window—In packet radio at the link layer, the range of frame numbers within the control field used to set the maximum number of frames that the sender may transmit before it receives an acknowledgment from the receiver.

X.25—An ITU-T packet-switching protocol Recommendation.