A companion software distribution pub/ntp/bsd_audio.tar.Z 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 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.
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
audio_IRIG_CLOSE
audio_IRIG_SETFORMAT
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 \n
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
/usr/include/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 epoch 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 ASCII space 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
audio_IRIG_BADDATA
?
. 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
audio_IRIG_BADCLOCK
audio_IRIG_OLDDATA
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.
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);
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.2 mV to 230 mV 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 us;
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 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
keyword can
be used to bias the clock-selection algorithm to favor the IRIG time,
which is ordinarily the best time available. The Mitigation Rules and the prefer
Keyword
page describes the operation of this keyword.
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 us) 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 ppsclock
streams module. This module can
be built from the ppsclock.tar.Z
distribution. It can be used to adjust time1
until the PPS signal and IRIG signal both show the same offset. The
ppsclock
streams module is described in the Line Disciplines and Streams Drivers page.
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.tar.Z
distribution. 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.