IRIG Support Using Sun SPARC Audio


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.

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.

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.

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.

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 \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 . . .
bits byte       number  <--00--><--01--><----     ----
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.

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

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.

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.

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 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.

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

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.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 prefer minpoll 4 maxpoll 4 # irig audio decoder
fudge 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.

David L. Mills (