             Audio IRIG Receiver for Precision Timekeeping

                         Revised 20 September 1993

Note: This information file is included in both the BSD audio driver
distribution (bsd_audio.tar.Z) and NTP Version 3 distribution
(xntp3.tar.Z) as the file README.irig. Both distributions can be
obtained via anonymous ftp from louie.udel.edu in the directory pub/ntp.

1. Introduction

This software distribution includes modifications to the BSD audio
driver for the Sun SPARCstation written by Van Jacobson and
collaborators at Lawrence Berkeley National Laboratory. The
modifications provide for the connection of a standard Inter-Range
Instrumentation Group (IRIG) timecode signal generator and the decoding
of the signal to produce data sufficient to synchronize a host clock to
the IRIG signal. There are several timing receivers now on the market
that can produce IRIG signals, including those made by Austron,
TrueTime, Odetics and Spectracom, among others. These data can be used
to precisely synchronize the host computer clock to within a few
microseconds without requiring level converters or pulse generators
necessary with the one-pulse-per-second signals also produced by these
receivers. The current implementation of the Network Time Protocol
Version 3 supports the modified BSD driver when installed in the SunOS
4.1.x kernel.

The specific IRIG signal format supported by the driver is designated
IRIG-B. It consists of an amplitude-modulated 1000-Hz sinewave, where
each symbol is encoded as ten full carrier cycles, or 10 ms in duration.
The symbols are distinguished using a pulse-width code, where 2 ms
corresponds to logic zero, 5 ms to logic one and 8 ms to a position
identifier used for symbol synchronization. The complete IRIG-B message
consists of a frame of ten fields, each field consisting of a nine
information symbols followed by a position identifier for a total frame
duration of one second. The first symbol in the frame is also a position
identifier to facilitate frame synchronization.

The IRIG-B signal encodes the day of year and time of day in binary-
coded decimal (BCD) format, together with a set of control functions,
which are not used by the driver, but included in the raw binary
timecode. Either the BCD timecode or the combined raw timecode and BCD
timecode can be returned in response to a read() system call. The BCD
timecode is in handy ASCII format: "ddd hh:mm:ss*" for convenience in
client programs. In this format the "*" status character is " " when the
driver is operating normally and "?" when errors may be present (see
below). In order to reduce residual errors to the greatest extent
possible, the driver computes a timestamp based on the value of the
kernel clock at the on-time epoch of the IRIG-B signal. In addition, the
driver automatically adjusts for slowly varying amplitude levels of the
IRIG-B signal and suppresses noise transients.

In operation the IRIG driver interprets the IRIG-B signal in real time,
synchronizes to the signal, demodulates the data bits and prepares the
data to be read later. At the on-time epoch a timestamp is captured from
the kernel clock and adjusted for the phase of the IRIG carrier signal
relative to the 8-kHz codec sample clock. When a client program issues a
read() request, the most recent timecode data, including a status byte
and the corrected timestamp, are stored in a structure and returned to
the caller. Depending on the frequency with which the driver is called,
this may result in old data or duplicate data or even invalid data,
should the driver be called before it has computed its first timestamp.

In practice, the resulting ambiguity causes few problems. The caller
converts the ASCII timecode returned by a read() system call to Unix
timeval format and subtracts it from the kernel timestamp provided by
the driver. The result is an adjustment that can be subtracted from the
kernel time, as returned in a gettimeofday() call, for example, to
correct for the deviation between IRIG time and kernel time. The result
can always be relied on to within plus/minus 128 microseconds, the audio
codec sampling interval, and ordinarily to within a few microseconds, as
determined by the interpolation algorithm.

2. Programming Interface

The IRIG driver modifications are integrated in the BSD audio driver
bsd_audio.c without affecting its usual functions in transmitting and
receiving ordinary speech, except when enabled by specific ioctl()
system calls. However, the driver cannot be used for both speech and
IRIG signals at the same time. Once activated by a designated ioctl()
call, the driver remains active until it is explicitly deactivated by
another ioctl() call. This allows applications to configure the audio
device and pass the pre-configured driver to other applications. Since
the driver is currently only a receiver, it does not affect the
operation of the BSD audio output driver.

Data are read using the standard read() system call. Since the output
formats have constant lengths, the application receives the data into a
fixed-length buffer or structure. The read() call never blocks; it
simply returns the most recent IRIG data received during the last
second. It may happen that, due to unavoidable race conditions in the
kernel, data for other than the most recent second are returned. The
driver's internal data structure is updated as an atomic unit; thus, the
entire structure is valid, even if it contains old data. This should
cause no problems, since in the intended application the driver is
called at regular intervals by a time-synchronization daemon such as
NTP. The daemon can determine the validity of the time indication by
checking the timecode or status byte returned with the data.

The header file bsd_audioirig.h  defines the irig_time structure and
ioctl() codes used by the driver. Following are those codes specific to
the IRIG function of the driver. Unless indicated otherwise, the (third)
argument of the ioctl() system call points to an integer or string.

AUDIO_IRIG_OPEN

     This command activates the IRIG receiver. The audio driver must be
     opened with this command before other commands can be issued. The
     argument is ignored. When the IRIG receiver is initialized, all
     internal data are purged and any buffered data are lost.

AUDIO_IRIG_CLOSE

     This command deactivates the IRIG receiver. The argument is
     ignored. The buffers are purged and any buffered time data are
     lost. The original BSD audio driver functions are enabled and it
     resumes operating normally.

AUDIO_IRIG_SETFORMAT

     The argument is a pointer to an integer designating the output
     format for the IRIG data. There are currently two formats defined,
     0 (default) and 1. If an invalid format is selected, the default
     format is used.

The data returned by a read() system call in format 0 is a character
string in the format "ddd hh:mm:ss*\n", which consists of 13 ASCII
characters followed by a newline terminator for a total of 14
characters. The "*" status character is an ASCII space " " if the status
byte determined by the driver is zero and "?" if not. This format is
intended to be used with simple user programs that care only about the
time to the nearest second.
The data returned by a read() system call in format 1 is a structure
defined in the bsd_audioirig.h header file:

     struct irig_time {
          struct timeval stamp;    /* timestamp */
          u_char bits[13];         /* 100 irig data bits */
          u_char status;           /* status byte */
          char time[14];           /* time string */
     };

The irig-time.stamp is a pair of 32-bit longwords in Unix timeval
format, as defined in the sys/time.h header file. The first word is the
number of seconds since 1 January 1970, while the second is the number
of microseconds in the current second. The timestamp is captured at the
most recent on-time instant of the IRIG timecode and applies to all
other values returned in the irig_time structure.

The irig_time.bits[13] is a vector of 13 bytes to hold the 100-bit,
zero-padded raw binary timecode, packed 8 symbols per byte. The symbol
encoding maps IRIG one to 1 and both IRIG zero and IRIG position
identifier to 0. The order of encoding is illustrated by the following
diagram (the padding bits are represented by xxxx, which are set to
zero):

IRIG symbol number  00000000001111111111 . . . 8888889999999999xxxx
                    01234567890123456789 . . . 4567890123456789xxxx
                    -----------------------------------------------
bits byte number    <--00--><--01--><----     ----><--11--><--12-->
bits bit in byte    01234567012345670123 . . . 45670123456701234567

The irig_time.status is a single byte with bits defined in the
bsd_audioirig.h header file. In ordinary operation all bits of the
status byte are zero and the " " status character is set in the ASCII
timecode. If any of these bits are nonzero, the "?" status character is
set in the ASCII timecode.

AUDIO_IRIG_BADSIGNAL

     The signal amplitude is outside tolerance limits, either in
     amplitude or modulation depth. The indicated time may or may not be
     in error. If the signal is too high, it may be clipped by the
     codec, so that the pulse width cannot be reliably determined. If
     too low, it may be obscured by noise. The nominal expectation is
     that the peak amplitude of the signal be maintained by the codec
     AGC at about 10 dB below the clipping level and that the modulation
     index be at least 0.5 (6 dB).

AUDIO_IRIG_BADDATA

     An invalid hex code (A through F) has been found where BCD data is
     expected. The ASCII representation of the invalid code is set to
     "?". Errors of this type are most likely due to noise on the IRIG
     signal due to ground loops, coupling to other noise sources, etc.

AUDIO_IRIG_BADSYNC

     A code element has been found where a position identifier should be
     or a position identifier has been found where a code element should
     be. The time is meaningless and should be disregarded. Errors of
     this type can be due to severe noise on the IRIG signal due to
     ground loops, coupling to other noise sources, etc., or during
     initial acquisition of the signal.

AUDIO_IRIG_BADCLOCK

     Some IRIG timecode generators can indicate whether or not the
     generator is operating correctly or synchronized to its source of
     standard time using a designated field in the raw binary timecode.
     Where such information is available and the IRIG decoder can detect
     it, this bit is set when the generator reports anything except
     normal operating conditions.

AUDIO_IRIG_OLDDATA

     The IRIG time has not changed since the last time it was returned
     in a read() call. This is not normally considered an error, unless
     it persists for longer than a few seconds, in which case it
     probably indicates a hardware problem.

The irig_time.time[14] vector is a character string in the format "ddd
hh:mm:ss*\0", which consists of 13 ASCII characters followed by a zero
terminator. The "*" status character is an ASCII space " " if the status
byte is zero and "?" if not. This format is identical to format 0,
except that in format 1 the time string is null-terminated.

2.1. Programming Example

The following pseudo-code demonstrates how the IRIG receiver may be used
by a simple user program. Of course, real code should include error
checking after each call to ensure the driver is communicating properly.
It should also verify that the correct fields in the structure are being
filled by the read() call.

     include "bsd_audioirig.h"

     int format = 1;
     struct irig_time it;

     Audio_fd = open("/dev/audio", O_RDONLY);
     ioctl(Audio_fd, AUDIO_IRIG_OPEN, NULL);
     ioctl(Audio_fd, AUDIO_IRIG_SETFORMAT,&format);
     while (condition)
          read(Audio_fd, &it, sizeof(it);
          printf("%s\n", it.time);
     ioctl(Audio_fd, AUDIO_IRIG_CLOSE, NULL);
     close(Audio_fd);

3. Implementation and Configuration Notes

The signal level produced by most IRIG-equipped radios is on the order
of a few volts peak-peak, which is far larger than the audio codec can
accept; therefore, an attenuator in the form of a voltage divider is
needed. The codec can handle IRIG signals at the microphone input from
4.2mV to 230mV peak-peak. A suitable attenuator conists of a series-
connected 100K-Ohm resistor at the input and a parallel-connected 1K-Ohm
resistor at the output, both contained along with suitable connectors in
a small aluminum box. The exact values of these resistors are not
critical, since the IRIG driver includes an automatic level-adjustment
capability.

For the most accurate time using the IRIG signal and a particular radio,
it may be necessary to adjust the time1 parameter of the fudge command
to compensate for the codec delay and any additional delay due to IRIG
processing in the radio itself. Since the codec samples at an 8-kHz
rate, the average delay is about 62 usec; however, the delays due to the
radios and IRIG signals themselves can vary. For instance, in the
Austron recievers the IRIG delay is essentially zero, while in the
Spectracom receivers the delay is about 240 usec relative to the 1-pps
signal. In addition, the poll interval can be reduced from the usual 64
seconds to 16 seconds to reduce wander of the local hardware clock.
Finally, the prefer parameter can be used to bias the clock-selection
algorithm to favor the IRIG time, which is ordinarily the best time
available. For example, the following two lines in the NTP configuration
file ntp.conf are appropriate for the Spectracom Netclock/1 WWVB
Synchronized Clock with IRIG Option:

server 127.127.6.0 prefer minpoll 4 maxpoll 4 # irig audio decoder
fudge 127.127.6.0 time1 0.0005

The time1 value of .0005 s (500 usec) was determined by actual
measurement. Since the IRIG delay in Austron receivers is essentially
zero, the fudge command is not necessary with these receivers. The
correct value in case of other radios may have to be determined by
actual measurement. A convenient way of doing this is to configure the
PPSPPS feature in the NTP Version 3 distribution and adjust time1 until
the 1-pps signal and IRIG signal both show the same offset.

The modified BSD driver includes both the modified driver itself
bsd_audio.c and the IRIG header file bsd_audioirig.h, as well as
modified header files bsd_audiovar.h and bsd_audioio.h. The driver is
installed in the same way as described in the BSD driver documentation,
with the addition of the following define in the kernel configuration
file:

options   AUDIO_IRIG          # IRIG driver

This causes the IRIG code to be included in the BSD driver, as well as a
C-coded codec interrupt routine which replaces the assembly-coded
routine and provides the IRIG functionality. While the C-coded routine
is somewhat slower than the assembly-coded routine, the extra overhead
is not expected to be significant. Note that the IRIG driver calls the
kernel routine microtime() as included in the ppsclock directory of the
NTP Version 3 distribution xntp3. It is highly recommended that this
routine be installed in the kernel configuration as well. The
instructions for doing this are contained in the ppsclock directory of
the xntp3 distribution.

Roy LeCates <lecates@udel.edu> and David Mills <mills@udel.edu>
Electrical Engineering Department
University of Delaware
Newark, DE 19716
302 831 8247 fax 302 831 4316

24 August 1993
