Audio IRIG Receiver for Precision Timekeeping
---------------------------------------------

Note: The files necessary to implement the functions described in the
following memorandum are available directly from the authors. Specific
details on how to make them available at archive sites are to be
determined.

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 used
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 appropriate time 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 captured 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 timestamp provided by the
driver. The result is a correction 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.

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. This deserves some consideration, because internal buffers may
fill up if a read() is not performed periodically (approximately once
per two minutes). If this occurs, the next time data read may be old.
However, 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

     The audio driver must be opened with this ioctl before other ioctls
     can be issued. The argument is ignored. This command activates the
     IRIG receiver. 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 is 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 14 ASCII
characters followed by a newline terminator. 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 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
idertifier 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 acquision 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.

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

contains a string similar to that of format 0, except this string is
null-terminated.

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);

Implementation and Configuration Notes

Since the signal level produced by most IRIG-equipped radios is far
larger than the audio codec can accept, an attenuator in the form of a
voltage divider is needed. This can take the form of a resistive divider
composed 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 value of
these resistors is not critical, since the IRIG driver includes an
automatic level-adjustment capability.

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 one
exception. The assembly-coded hardware interrupt routine is not used; in
replacement, a C-code driver specific to the IRIG functions is provided.
This requires the following two lines in the kernel configuration file:

options   AUDIO_IRIG          # IRIG driver
options   AUDIO_C_HANDLER     # IRIG audio handler

The first of these causes the IRIG code to be included in the BSD
driver; the second causes the assembly-coded codec interrupt routine to
be disabled, since the IRIG functions require a more complex C-coded
routine. 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

14 August 1993
