]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
Many files:
authorHarlan Stenn <stenn@ntp.org>
Tue, 4 Jan 2000 06:01:09 +0000 (06:01 -0000)
committerHarlan Stenn <stenn@ntp.org>
Tue, 4 Jan 2000 06:01:09 +0000 (06:01 -0000)
  * ntpd/refclock_wwv.c:
  * ntpd/refclock_chu.c:
  * libntp/icom.c:
  * libntp/Makefile.am:
  * include/icom.h:
  * html/driver7.htm:
  * html/driver36.htm:
  Support for ICOM.  The WWV/H driver, by the way, is getting truly
  awesome.  The CHU autotune function works okay as it is.  I'd like
  to find somebody else to test the audio drivers just to make sure
  I haven't done something stupid.  There is a new define ICOM
  intended for the driver autotune function; however, I crafted the
  thing in much the same way as the refclock_atom.c thing - it tries
  to open /dev/icom and, if that fails, goes quietly to sleep.
  From: Dave Mills <mills@udel.edu>

bk: 38718ca5a0cprQP9ccMyyHx38tJzBg

ChangeLog
html/driver36.htm
html/driver7.htm
include/icom.h [new file with mode: 0644]
libntp/Makefile.am
libntp/Makefile.in
libntp/icom.c [new file with mode: 0644]
ntpd/refclock_chu.c
ntpd/refclock_wwv.c

index 0ecd1c4e3a3e7c5ec9ba8feee5d7c579dfe8107e..9163bd950f200c98b7ea2ca68b722e07788de215 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2000-01-04  Harlan Stenn  <stenn@whimsy.udel.edu>
+
+       * ntpd/refclock_wwv.c: 
+       * ntpd/refclock_chu.c: 
+       * libntp/icom.c: 
+       * libntp/Makefile.am: 
+       * include/icom.h: 
+       * html/driver7.htm: 
+       * html/driver36.htm: 
+       Support for ICOM.  The WWV/H driver, by the way, is getting truly
+       awesome.  The CHU autotune function works okay as it is.  I'd like
+       to find somebody else to test the audio drivers just to make sure
+       I haven't done something stupid.  There is a new define ICOM
+       intended for the driver autotune function; however, I crafted the
+       thing in much the same way as the refclock_atom.c thing - it tries
+       to open /dev/icom and, if that fails, goes quietly to sleep.
+       From: Dave Mills <mills@udel.edu>
+
 2000-01-03  Harlan Stenn  <stenn@whimsy.udel.edu>
 
        * ntpd/refclock_oncore.c (oncore_read_config): Patches and cleanup
index b3a12fcbf649ccf7d949706cfdb95f0b255316aa..5fe3e4d3aaae69b4eec8c7828b4f4fbab7ac45ca 100644 (file)
@@ -9,17 +9,21 @@ Radio WWV/H Audio Demodulator/Decoder
 Address: 127.127.36.<I>u</I>
 <br>Reference ID: <tt>WWV</tt> or <tt>WWVH</tt>
 <br>Driver ID: <tt>WWV_AUDIO</tt>
+<br>Autotune Port: <tt>/dev/icom</tt>; 1200 baud, 8-bits, no parity
 <br>Audio Device: <tt>/dev/audio</tt> and <tt>/dev/audioctl</tt>
 
 <h4>Description</h4>
 
 This driver synchronizes the computer time using data encoded in
 shortwave radio transmissions from NIST time/frequency stations WWV in
-Ft. Collins, CO, and WWVH in Kauai, HI. When used in conjunction with a
-shortwave receiver and the audio codec native to some workstations, it
-implements a radio clock with nominal timing error less than 125 <font
-face=Symbol>m</font>s when tracking one of the stations and frequency
-drift less than 0.5 PPM when not tracking either station.
+Ft. Collins, CO, and WWVH in Kauai, HI. Transmissions are made
+continuously on 2.5, 5, 10, 15 and 20 MHz. An ordinary shortwave
+receiver can be tuned manually to one of these frequencies or, in the
+case of ICOM receivers, the receiver can be tuned automatically by this
+program as propagation conditions change throughout the day and night.
+The performance of this driver when tracking one of the stations is
+ordinarily better than 1 ms in time with frequency drift less than 0.5
+PPM when not tracking either station.
 
 <p>The demodulation and decoding algorithms used by this driver are
 based on a machine language program developed for the TAPR DSP93 DSP
@@ -82,28 +86,28 @@ loop to discipline the codec sample clock and thus the demodulator
 phase.
 
 <p>The data bit probabilities are determined from the subcarrier
-envelope using a threshold-corrected slicer. The envelope amplitude 30
-ms from the beginning of the second establishes the minimum (noise
-floor) value, while the amplitude 200 ms from the beginning establishes
-the maximum (signal peak) value. The slice level is midway between these
-two values. The negative-going envelope transition at the slice level
-establishes the length of the data pulse, which in turn establish
-probabilities for binary zero (P0) or binary one (P1). The values are
-established by linear interpolation between the pulse lengths for P0
-(300 ms) and P1 (500 ms) so that the sum is equal to one. If the program
-has not synchronized to the minute pulse, or if the data bit amplitude,
-signal/noise ratio (SNR) or length are below thresholds, the bit is
-considered invalid and all three probabilities are set to zero.
-
-<p>The difference between the P1 and P0 probabilities, called the
-likelihood, for each data bit is exponentially averaged in a set of 60
-accumulators, one for each second, to determine the semi-static
-miscellaneous bits, such as DST indicator, leap second warning and DUT1
-correction. In this design, an average value larger than a positive
-threshold is interpreted as a hit on one and a value smaller than a
-negative threshold as a hit on zero. Values between the two thresholds,
-which can occur due to signal fades or loss of signal, are interpreted
-as a miss, and result in no change of indication.
+envelope using a threshold-corrected slicer. The averaged envelope
+amplitude 30 ms from the beginning of the second establishes the minimum
+(noise floor) value, while the amplitude 200 ms from the beginning
+establishes the maximum (signal peak) value. The slice level is midway
+between these two values. The negative-going envelope transition at the
+slice level establishes the length of the data pulse, which in turn
+establish probabilities for binary zero (P0) or binary one (P1). The
+values are established by linear interpolation between the pulse lengths
+for P0 (300 ms) and P1 (500 ms) so that the sum is equal to one. If the
+program has not synchronized to the minute pulse, or if the data bit
+amplitude, signal/noise ratio (SNR) or length are below thresholds, the
+bit is considered invalid and all three probabilities are set to zero.
+
+<p>The difference between the P1 and P0 probabilities, or likelihood,
+for each data bit is exponentially averaged in a set of 60 accumulators,
+one for each second, to determine the semi-static miscellaneous bits,
+such as DST indicator, leap second warning and DUT1 correction. In this
+design, an average value larger than a positive threshold is interpreted
+as a hit on one and a value smaller than a negative threshold as a hit
+on zero. Values between the two thresholds, which can occur due to
+signal fades or loss of signal, are interpreted as a miss, and result in
+no change of indication.
 
 <p>The BCD digit in each digit position of the timecode is represented
 as four data bits, all of which must be valid for the digit itself to be
@@ -162,24 +166,22 @@ may be available at different times or even at the same time. Since the
 propagation times from either station are almost always different, each
 station must be reliably identified before attempting to set the clock.
 
-<p>Station identification is done using the 800-ms minute pulse
-transmitted by each station. In the acquisition phase the entire minute
-is searched using both the WWV and WWVH using matched filters and a
-pulse gate discriminator similar to that found in radar acquisition and
-tracking receivers. The peak amplitude found determines a range gate and
-window where the next pulse is expected to be found. The minute is
-scanned again to verify the peak is indeed in the window and with
-acceptable amplitude, SNR and jitter. At this point the receiver begins
-to track the second sync pulse and operate as above until the clock is
-set.
-
-<p>Once the clock is set, the range gate is fixed and only energy within
-the window is considered for the sync pulses. As long as the minute
-pulse from either station has acceptable amplitude and SNR, tracking
-functions can be enabled for that station. A compare counter keeps track
-of the number of minutes when the minute pulse jitter is less than a
-threshold. When both stations are available, the one with the maximum
-compare count is selected.
+<p>Station identification uses the 800-ms minute pulse transmitted by
+each station. In the acquisition phase the entire minute is searched
+using both the WWV and WWVH using matched filters and a pulse gate
+discriminator similar to that found in radar acquisition and tracking
+receivers. The peak amplitude found determines a range gate and window
+where the next pulse is expected to be found. The minute is scanned
+again to verify the peak is indeed in the window and with acceptable
+amplitude, SNR and jitter. At this point the receiver begins to track
+the second sync pulse and operate as above until the clock is set.
+
+<p>Once the minute is synchronized, the range gate is fixed and only
+energy within the window is considered for the minute sync pulse. A
+compare counter increments by one if the minute pulse has acceptable
+amplitude, SNR and jitter and decrements otherwise. This is used as a
+quality indicator and reported in the timecode and also for the autotune
+function described below.
 
 <h4>Performance</h4>
 
@@ -230,23 +232,22 @@ It then acquires second sync, which can take up to several minutes,
 depending on signal quality. At the same time the program accumulates
 likelihood values for each of the nine digits of the clock, plus the
 seven miscellaneous bits included in the WWV/H transmission format. The
-minute units digit is decoded first and, when several repetitions have
-compared correctly, the remaining eight digits are decoded. When several
+minute units digit is decoded first and, when five repetitions have
+compared correctly, the remaining eight digits are decoded. When five
 repetitions of all nine digits have decoded correctly, which normally
 takes 15 minutes with good signals and up to an hour when buried in
-noise, the clock is set and is selectable to discipline the system
-clock.
-
-<p>As long as the clock is set or verified and the second sync is
-acquired, the system clock offsets are provided once each second to the
-reference clock interface, where they are saved in a buffer. At the end
-of each minute, the buffer samples are groomed by the median filter and
-trimmed-mean averaging functions. Using these functions, the system
-clock can in principle be disciplined to a much finer resolution than
-the 125-<font face=Symbol>m</font>s sample interval would suggest,
-although the ultimate accuracy is probably limited by propagation delay
-variations due to ionspheric
-height variations.
+noise, and no alarms are raised, the clock is set (or verified) and is
+selectable to discipline the system clock.
+
+<p>As long as the clock is set or verified, the system clock offsets are
+provided once each second to the reference clock interface, where they
+are saved in a buffer. At the end of each minute, the buffer samples are
+groomed by the median filter and trimmed-mean averaging functions. Using
+these functions, the system clock can in principle be disciplined to a
+much finer resolution than the 125-<font face=Symbol>m</font>s sample
+interval would suggest, although the ultimate accuracy is probably
+limited by propagation delay variations due to ionspheric height
+variations.
 
 <p>As long as signals are available, the clock frequency is disciplined
 for use during times when the signals are unavailable. The algorithm
@@ -262,29 +263,28 @@ variations.
 WWVH signals may appear alone, together or not at all. When the program
 is first started, the NTP reference identifier appears as <tt>NONE</tt>.
 When the program has acquired one or both stations and mitigated which
-one is best, it sets the station identifier character in the timecode as
-described below. In addition, the NTP reference identifier is set to the
-station callsign. If the propagation delays has been properly set with
-the <tt>fudge time1</tt> (WWV) and <tt>fudge time2</tt> (WWVH) commands
-in the configuration file, handover from one station to the other will
-be seamless.
+one is best, it sets the station identifier in the timecode as described
+below. In addition, the NTP reference identifier is set to the station
+callsign. If the propagation delays has been properly set with the
+<tt>fudge time1</tt> (WWV) and <tt>fudge time2</tt> (WWVH) commands in
+the configuration file, handover from one station to the other will be
+seamless.
 
 <p>Once the clock has been set for the first time, it will appear
 reachable and selectable to discipline the system clock, even if the
 broadcast signal fades to obscurity. A consequence of this design is
 that, once the clock is set, the time and frequency are disciplined only
 by the second sync pulse and the clock digits themselves are driven by
-the clock state machine and cannot be changed, even if the maximum
-likelihood digits disagree. However, as long as the clock is set
-correctly, it will continue to read correctly after a period of signal
-loss, as long as it does not drift more than 500 ms from the correct
-time. Assuming the clock frequency can be disciplined within 1 PPM, the
-clock could coast without signals for some 5.8 days without exceeding
-that limit. If for some reason this did happen, the clock would be in
-the wrong second and would never resynchronize. To protect against this
-most unlikely situation, if after four days with no signals, the clock
-is considered unset and resumes the synchronization procedure from the
-beginning.
+the clock state machine and ordinarily never changed. However, as long
+as the clock is set correctly, it will continue to read correctly after
+a period of signal loss, as long as it does not drift more than 500 ms
+from the correct time. Assuming the clock frequency can be disciplined
+within 1 PPM, the clock could coast without signals for some 5.8 days
+without exceeding that limit. If for some reason this did happen, the
+clock would be in the wrong second and would never resynchronize. To
+protect against this most unlikely situation, if after four days with no
+signals, the clock is considered unset and resumes the synchronization
+procedure from the beginning.
 
 <p>To work well, this program needs a communications receiver with good
 audio response at 100 Hz. Most shortwave and communications receivers
@@ -298,6 +298,46 @@ can affect data recovery under marginal conditions. Although not tested,
 it would seem very likely that a cheap shortwave receiver could function
 just as well as an expensive communications receiver.
 
+<h4>Autotune</h4>
+
+<p>The driver includes provisions to automatically tune the radio in
+response to changing radio propagation conditions throughout the day and
+night. The radio interface is compatible with the ICOM CI-V standard,
+which is a bidirectional serial bus operating at TTL levels. The bus can
+be connected to a standard serial port using a level converter such as
+the CT-17. The serial port speed is presently compiled in the program,
+but can be changed in the <tt>icom.h</tt> header file.
+
+<p>Each ICOM radio is assigned a unique 8-bit ID select code, usually
+expressed in hex format. To activate the CI-V interface, the
+<tt>mode</tt> keyword of the <tt>server</tt> configuration command
+specifies a nonzero select code in decimal format. A table of ID select
+codes for the known ICOM radios is given below. A missing <tt>mode</tt>
+keyword or a zero argument leaves the interface disabled. The driver
+will attempt to open the device <tt>/dev/icom</tt> and, if successful
+will activate the autotune function and tune the radio to each operating
+frequency in turn while attempting to acquire minute sync from either
+WWV or WWVH.
+
+<p>Once acquiring minute sync, the program operates as above to acquire
+second sync and set the clock. However, during the three seconds
+beginning at second 58 of each minute it tunes the radio to one of the
+five broadcast frequencies to measure the sync pulse and data pulse
+amplitudes and SNR and update the compare counter. Each of the five
+frequencies are probed in a five-minute rotation to build a database of
+current propagation conditions for all signals that can be heard at the
+time. At the end of each rotation, a mitigation procedure scans the
+database and retunes the radio to thebest frequency and station found.
+
+<p>For the autotune function to work well, the radio should be set for a
+fast AGC recovery time. This is most important while tracking a strong
+signal, which is normally the case, and then probing another frequency,
+which may have much weaker signals.
+
+<p>The driver is liberal in what it assumes of the configuration. If the
+<tt>/dev/icom</tt> link is not present or the open fails or the CI-V bus
+or radio is inoperative, the driver quietly gives up with no harm done.
+
 <h4>Debugging Aids</h4>
 
 <p>The most convenient way to track the program status is using the
@@ -352,15 +392,13 @@ formats:
 working on the unit digit of the minute. They show the results of
 decoding each bit of the transmitted timecode.
 
-<p><tt>wwv3 ss stat sigl alrm ampl phas snr prob like</tt>
-
+<p><tt>wwv3 ss stat sigl ampl phas snr prob like</tt>
 <p>where <tt>ss</tt>, <tt>stat</tt> and <tt>sigl</tt> are as above,
-<tt>alrm</tt> is the alarm word, <tt>ampl</tt> the subcarrier amplitude,
-<tt>phas</tt> the subcarrier phase, <tt>snr</tt> the subcarrier SNR,
-<tt>prob</tt> the bit probability and <tt>like</tt> the bit likelihood.
-An example is:
+<tt>ampl</tt> is the subcarrier amplitude, <tt>phas</tt> the subcarrier
+phase, <tt>snr</tt> the subcarrier SNR, <tt>prob</tt> the bit
+probability and <tt>like</tt> the bit likelihood. An example is:
 
-<p><tt>wwv3 28 0323  4122 e0f8  4286     0  24.8 -5545 -1735</tt>
+<p><tt>wwv3 28 0323  4122 4286     0  24.8 -5545 -1735</tt>
 
 <p>Here the program has acquired minute and second sync, but has not yet
 set the clock. However, it has just decoded bit 28 of the minute. The
@@ -435,13 +473,23 @@ The WWV minute pulse amplitude and SNR are well above the threshold
 1 sample relative to the last one and 1 sample relative to the second
 sync pulse.
 
+<p>Format <tt>wwv5</tt> messages are produced once per minute by the WWV
+and WWVH autotune station processes which precess over the five channels
+or frequencies available. These messages are described in the Autotune
+section.
+
+<p>If the CI-V interface for ICOM radios is active, a debug level
+greater than 1 will produce a trace of the CI-V command and response
+messages. Interpretation of these messages requires knowledge of the
+CI-V protocol, which is beyond the scope of this document.
+
 <h4>Monitor Data</h4>
 
 When enabled by the <tt>filegen</tt> facility, every received timecode
 is written to the <tt>clockstats</tt> file in the following format:
 
 <pre>
-        sq yy ddd hh:mm:ss.fff ld du lset agc stn errs freq cons
+        sq yy ddd hh:mm:ss.fff ld du lset agc stn rfrq errs freq cons
 
         s       sync indicator
         q       quality character
@@ -455,7 +503,7 @@ is written to the <tt>clockstats</tt> file in the following format:
         dut     DUT sign and magnitude
         lset    minutes since last set
         agc     audio gain
-        stn     transmitter identifier
+        stn     station identifier and frequency
         comp    minute sync compare counter
         errs    bit error counter
         freq    frequency offset
@@ -478,7 +526,6 @@ set.</dd>
 <dd>The quality character is a four-bit hexadecimal code showing which
 alarms have been raised. Each bit is associated with a specific alarm
 condition according to the following:
-
 <dl>
 
 <dt><tt>0x8</tt>
@@ -536,16 +583,14 @@ to 255. Ordinarily, the receiver audio gain control or IRIG level
 control should be set for a value midway in this range.
 
 <dt><tt>stn</tt>
-<dd>The station identifier shows the current source of minute sync,
-<tt>C</tt> for WWV or <tt>H</tt> for WWVH, as long as that station is
-being heard. If neither station is being heard, the identifier becomes
-<tt>X</tt> and, if both are heard, <tt>M</tt>.</dd>
+<dd>The station identifier shows the station, <tt>C</tt> for WWV or
+<tt>H</tt> for WWVH, and frequency being tracked. If neither station is
+heard on any frequency, the station identifier shows <tt>X</tt>.</dd>
 
 <dt><tt>comp</tt>
 <dd>The minute sync compare counter is useful to determine the quality
 of the minute sync signal and can range from 0 (no signal) to 5
 (best).</dd>
-
 <dt><tt>errs</tt>
 <dd>The bit error counter is useful to determine the quality of the data
 signal received in the most recent minute. It is normal to drop a couple
@@ -567,6 +612,87 @@ time and frequency.</dd>
 
 </dl>
 
+<h4>Modes</h4>
+
+<p>The <tt>mode</tt> keyword of the <tt>server</tt> configuration
+command specifies the ICOM ID select code. A missing or zero argument
+disables the CI-V interface. Following are the ID select codes for the
+known radios.
+
+<p><table cols=6 width=100%>
+
+<tr>
+<td>Radio</td>
+<td>Hex</td>
+<td>Decimal</td>
+<td>Radio</td>
+<td>Hex</td>
+<td>Decimal</td>
+</tr>
+
+<tr>
+<td>IC725</td>
+<td>0x28</td>
+<td>40</td>
+<td>IC781</td>
+<td>0x26</td>
+<td>38</td>
+</tr>
+
+<tr>
+<td>IC726</td>
+<td>0x30</td>
+<td>48</td>
+<td>R7000</td>
+<td>0x08</td>
+<td>8</td>
+</tr>
+
+<tr>
+<td>IC735</td>
+<td>0x04</td>
+<td>4</td>
+<td>R71</td>
+<td>0x1A</td>
+<td>26</td>
+</tr>
+<tr>
+<td>IC751</td>
+<td>0x1c</td>
+<td>28</td>
+<td>R7100</td>
+<td>0x34</td>
+<td>52</td>
+</tr>
+<tr>
+<td>IC761</td>
+<td>0x1e</td>
+<td>30</td>
+<td>R72</td>
+<td>0x32</td>
+<td>50</td>
+</tr>
+
+<tr>
+<td>IC765</td>
+<td>0x2c</td>
+<td>44</td>
+<td>R8500</td>
+<td>0x4a</td>
+<td>74</td>
+</tr>
+
+<tr>
+<td>IC775</td>
+<td>0x46</td>
+<td>68</td>
+<td>R9000</td>
+<td>0x2a</td>
+<td>42</td>
+</tr>
+
+</table>
+
 <h4>Fudge Factors</h4>
 
 <dl>
@@ -595,16 +721,13 @@ described above.
 <dd>Specifies the microphone port if set to zero or the line-in port if
 set to one. It does not seem useful to specify the compact disc player
 port.</dd>
-
 <dt><tt>flag3 0 | 1</tt></dt>
 <dd>Enables audio monitoring of the input signal. For this purpose, the
 speaker volume must be set before the driver is started.</dd>
 
 <dt><tt>flag4 0 | 1</tt></dt>
 <dd>Enable verbose <tt>clockstats</tt> recording if set.</dd>
-
 </dl>
-
 <h4>Additional Information</h4>
 
 <A HREF="refclock.htm">Reference Clock Drivers</A>
index 7953df99b65252b6c1e26d236e900d53293a9154..2a64d8fde5eeec07204edcdf8c46d443fec51cc4 100644 (file)
@@ -9,7 +9,8 @@ Radio CHU Audio Demodulator/Decoder
 Address: 127.127.7.<I>u</I>
 <br>Reference ID: <tt>CHU</tt>
 <br>Driver ID: <tt>CHU</tt>
-<br>Serial Port: <tt>/dev/chu<I>u</I></tt>; 300 baud, 8-bits, no parity
+<br>Modem Port: <tt>/dev/chu<I>u</I></tt>; 300 baud, 8-bits, no parity
+<br>Autotune Port: <tt>/dev/icom</tt>; 1200 baud, 8-bits, no parity
 <br>Audio Device: <tt>/dev/audio</tt> and <tt>/dev/audioctl</tt>
 
 <h4>Description</h4>
@@ -19,13 +20,15 @@ transmissions from Canadian time/frequency station CHU in Ottawa,
 Ontario. Transmissions are made continuously on 3330 kHz, 7335 kHz and
 14670 kHz in upper sideband, compatible AM mode. An ordinary shortwave
 receiver can be tuned manually to one of these frequencies or, in the
-case of ICOM receivers, the receiver can be tuned automatically using
-the <tt>minimuf</tt> and <tt>icom</tt> programs as propagation
-conditions change throughout the day and night.
+case of ICOM receivers, the receiver can be tuned automatically as
+propagation conditions change throughout the day and night. The
+performance of this driver when tracking the station is ordinarily
+better than 1 ms in time with frequency drift less than 0.5 PPM when not
+tracking the station.
 
 <p>While there are currently no known commercial CHU receivers, a simple
 but effective receiver/demodulator can be constructed from an ordinary
-shortwave receiver and Bell 103 compatible, 300-bps modem or modem chip,
+shortwave receiver and Bell 103 compatible, 300-b/s modem or modem chip,
 as described in the <a href=pps.htm>Pulse-per-second (PPS) Signal
 Interfacing</a> page. The driver can be compiled to use a modem to
 receive the radio signal and demodulate the data. Alternatively, the
@@ -103,11 +106,146 @@ of timestamps determined in the range 0-60. For a valid timecode,
 <tt>bcnt</tt> must be at least 3, <tt>dist</tt> must be greater than
 <tt>bcnt</tt> and <tt>tsmp</tt> must be at least 20.
 
+<h4>Program Operation</h4>
+
+<p>The program consists of four major parts: the DSP modem, maximum
+likelihood UART, burst assembler and majority decoder. The DSP modem
+demodulates Bell 103 modem answer-frequency signals; that is, frequency-
+shift keyed (FSK) tones of 2225 Hz (mark) and 2025 Hz (space). This is
+done using a 4th-order IIR filter and limiter/discriminator with 500-Hz
+bandpass centered on 2125 Hz and followed by a FIR raised-cosine lowpass
+filter optimized for the 300-b/s data rate. Alternately, the driver can
+be compiled to delete the modem and input 300 b/s data directly from an
+external modem via a serial port.
+
+<p>The maximum likelihood UART is implemented using a set of eight
+11-stage shift registers, one for each of eight phases of the 300-b/s
+bit clock. At each phase a new baseband signal value from the DSP modem
+is shifted into the corresponding register and the maximum and minimum
+over all 11 samples computed. This establishes a slice level midway
+between the maximum and minimum over all stages. For each stage, a
+signal level above this level is a mark (1) and below is a space (0). A
+quality metric is calculated for each register with respect to the slice
+level and the a-priori signal consisting of a mark bit (previous stop
+bit), space (start) bit, eight arbitrary information bits and the first
+of the two mark (stop) bits.
+<p>The shift registers are processed in round-robin order as each modem
+value arrives until one of them shows a valid framing pattern consisting
+of a mark bit, space bit, eight arbitrary data bits and a mark bit. When
+found, the data bits from the register with the best metric is chosen as
+the maximum likelihood character and the UART begins to process the next
+character.
+
+<p>The burst assembler processes characters either from the maximum
+likelihood UART or directly from the serial port as configured. A burst
+begins when a character is received and is processed after a timeout
+interval when no characters are received. If the interval between
+characters is greater than two characters, but less than the timeout
+interval, the burst is rejected as a runt and a new burst begun. As each
+character is received, a timestamp is captured and saved for later
+processing.
+
+<p>A valid burst consists of ten characters in two replicated
+five-character blocks. A format B block contains the year and other
+information in ten hexadecimal digits. A format A block contains the
+timecode in ten decimal digits, the first of which is a framing code
+(6). The burst assembler must deal with cases where the first character
+of a format A burst is lost or is noise. This is done using the framing
+code to correct the phase, either one character early or one character
+late.
+
+<p>The burst distance is incremented by one for each bit in the first
+block that matches the corresponding bit in the second block and
+decremented by one otherwise. In a format B burst the second block is
+bit-inverted relative to the first, so a perfect burst of five 8-bit
+characters has distance -40. In a format A block the two blocks are
+identical, so a perfect burst has distance +40. Format B bursts must be
+perfect to be acceptable; however, format A bursts, which are further
+processed by the majority decoder, are acceptable if the distance is at
+least 28.
+
+<p>Each minute of transmission includes eight format A bursts containing
+two timecodes for each second from 31 through 39. The majority decoder
+uses a decoding matrix of ten rows, one for each digit position in the
+timecode, and 16 columns, one for each 4-bit code combination that might
+be decoded at that position. In order to use the character timestamps,
+it is necessary to reliably determine the second number of each burst.
+In a valid burst, the last digit of the two timecodes in the block must
+match and the value must be in the range 2-9 and greater than in the
+previous burst.
+
+<p>As each hex digit of a valid burst is processed, the value at the row
+corresponding to the digit position in the timecode and column
+corresponding to the code found at that position is incremented. At the
+end of each minute of transmission, each row of the decoding matrix
+encodes the number of occurrences of each code found at the
+corresponding position of the timecode. However, the first digit
+(framing code) is always 6, the ninth (second tens) is always 3 and the
+last (second units) changes for each burst, so are not used.
+
+<p>The maximum over all occurrences at each timecode digit position is
+the distance for that position and the corresponding code is the maximum
+likelihood candidate. If the distance is zero, the decoder assumes a
+miss; if the distance is not more than half the total number of
+occurrences, the decoder assumes a soft error; if two different codes
+with the same distance are found, the decoder assumes a hard error. In
+all these cases the decoder encodes a non-decimal character which will
+later cause a format error when the timecode is reformatted. The
+decoding distance is defined as the minimum distance over the first nine
+digits; the tenth digit varies over the seconds and is uncounted.
+
+<p>The result of the majority decoder is a nine-digit timecode
+representing the maximum likelihood candidate for the transmitted
+timecode in that minute. Note that the second and fraction within the
+minute are always zero and that the actual reference point to calculate
+timestamp offsets is backdated to the first second of the minute. At
+this point the timecode block is reformatted and the year, days, hours
+and minutes extracted along with other information from the format B
+burst, including DST state, DUT1 correction and leap warning. The
+reformatting operation checks the timecode for invalid code combinations
+that might have been left by the majority decoder and rejects the entire
+timecode if found.
+
+<p>If the timecode is valid, it is passed to the reference clock
+interface along with the backdated timestamp offsets accumulated over
+the minute. A perfect set of nine bursts could generate as many as 90
+timestamps, but the maximum the interface can handle is 60. These are
+processed by the interface using a median filter and trimmed-mean
+average, so the resulting system clock correction is usually much better
+than would otherwise be the case with radio noise, UART jitter and
+occasional burst errors.
+
+<h4>Autotune</h4>
+
+<p>The driver includes provisions to automatically tune the radio in
+response to changing radio propagation conditions throughout the day and
+night. The radio interface is compatible with the ICOM CI-V standard,
+which is a bidirectional serial bus operating at TTL levels. The bus can
+be connected to a standard serial port using a level converter such as
+the CT-17. The serial port speed is presently compiled in the program,
+but can be changed in the <tt>icom.h</tt> header file.
+
+<p>Each ICOM radio is assigned a unique 8-bit ID select code, usually
+expressed in hex format. To activate the CI-V interface, the
+<tt>mode</tt> keyword of the <tt>server</tt> configuration command
+specifies a nonzero select code in decimal format. A table of ID select
+codes for the known ICOM radios is given below. A missing <tt>mode</tt>
+keyword or a zero argument leaves the interface disabled. The driver
+will attempt to open the device <tt>/dev/icom</tt> and, if successful
+will tune the radio to 3.330 MHz. If after five minutes at this
+frequency not more than two format A bursts have been received for any
+minute, the driver will tune to 7.335 MHz, then to 14.670 MHz, then
+return to 3.330 MHz and continue in this cycle.
+
+<p>The driver is liberal in what it assumes of the configuration. If the
+<tt>/dev/icom</tt> link is not present or the open fails or the CI-V bus
+or radio is inoperative, the driver quietly gives up with no harm done.
+
 <h4>Radio Broadcast Format</h4>
 
 <p>The CHU time broadcast includes an audio signal compatible with the
 Bell 103 modem standard (mark = 2225 Hz, space = 2025 Hz). It consist of
-nine, ten-character bursts transmitted at 300 bps and beginning each
+nine, ten-character bursts transmitted at 300 b/s and beginning each
 second from second 31 to second 39 of the minute. Each character
 consists of eight data bits plus one start bit and two stop bits to
 encode two hex digits. The burst data consist of five characters (ten
@@ -119,14 +257,12 @@ characters are repeated in the opposite polarity.
 hex digits
 
 <p><tt>6dddhhmmss6dddhhmmss</tt>
-
 <p>The first ten digits encode a frame marker (<tt>6</tt>) followed by
 the day (<tt>ddd</tt>), hour (<tt>hh</tt>), minute (<tt>mm</tt>) and
 second (<tt>ss</tt>). Since format A bursts are sent during the
 third decade of seconds the tens digit of <tt>ss</tt> is always 3. The
 driver uses this to determine correct burst synchronization. These
 digits are then repeated with the same polarity.
-
 <p>Format B bursts are sent at second 31 of the minute in hex digits
 
 <p><tt>xdyyyyttaaxdyyyyttaa</tt>
@@ -158,7 +294,7 @@ likely to happen in our universe.</dd>
 
 <p>By design, the last stop bit of the last character in the burst
 coincides with 0.5 second. Since characters have 11 bits and are
-transmitted at 300 bps, the last stop bit of the first character
+transmitted at 300 b/s, the last stop bit of the first character
 coincides with 0.5 - 10 * 11/300 = 0.133 second. Depending on the UART,
 character interrupts can vary somewhere between the beginning of bit 9
 and end of bit 11. These eccentricities can be corrected along with the
@@ -190,9 +326,7 @@ received in seconds 32 through 39 of the minute:
 distance (0-40) and <tt>code</tt> the burst characters as received. Note
 that the hex digits in each character are reversed and the last ten
 digits inverted, so the burst
-
 <p><tt>11 40 1091891300ef6e76ecff</tt>
-
 <p>is interpreted as containing 11 characters with burst distance 40.
 The nibble-swapped timecode shows DUT1 +0.1 second, year 1998 and TAI -
 UTC 31 seconds.
@@ -214,13 +348,18 @@ Note that the hex digits in each character are reversed, so the burst
 field alignment 0, synchronization distance 16 and burst number 9. The
 nibble-swapped timecode shows day 58, hour 21, minute 29 and second 39.
 
+<p>If the CI-V interface for ICOM radios is active, a debug level
+greater than 1 will produce a trace of the CI-V command and response
+messages. Interpretation of these messages requires knowledge of the
+CI-V protocol, which is beyond the scope of this document.
+
 <h4>Monitor Data</h4>
 
 When enabled by the <tt>filegen</tt> facility, every received timecode
 is written to the <tt>clockstats</tt> file in the following format:
 
 <pre>
-        sq yy ddd hh:mm:ss.fff ld dut lset agc bcnt dist tsmp
+        sq yy ddd hh:mm:ss.fff ld dut lset agc rfrq bcnt dist tsmp
 
         s       sync indicator
         q       quality character
@@ -235,6 +374,7 @@ is written to the <tt>clockstats</tt> file in the following format:
         dut     DUT sign and magnitude in deciseconds
         lset    minutes since last set
         agc     audio gain (0-255)
+        rfrq    radio frequency
         bcnt    burst count
         dist    decoding distance
         tsmp    timestamps captured
@@ -257,7 +397,6 @@ alarms have been raised during the most recent minute. Each bit is
 associated with a specific alarm condition according to the following:
 
 <dl>
-
 <dt><tt>8</tt>
 <dd>Decoder alarm. A majority of repetitions for at least one digit of
 the timecode fails to agree.
@@ -283,7 +422,6 @@ detected a condition that may in future result in an error.
 <dt><tt>yyyy ddd hh:mm:ss.fff</tt></tt>
 <dd>The timecode format itself is self explanatory. Note that the
 Gregorian year is decoded directly from the transmitted timecode.</dd>
-
 <dt><tt>l</tt>
 <dd>The leap second warning is normally space, but changes to <tt>L</tt>
 if a leap second is to occur at the end of the month of June or
@@ -307,6 +445,10 @@ broadcast signal.</dd>
 to 255. Ordinarily, the receiver audio gain control or IRIG level
 control should be set for a value midway in this range.
 
+<dt><tt>rfrq</tt>
+<dd>The current radio frequency, if the CI-V interface is active, or 'X'
+if not.</dd>
+
 <dt><tt>bcnt</tt>
 <dd>The number of format A bursts received during the most recent minute
 bursts were received.</dd>
@@ -318,9 +460,91 @@ minute bursts were received.</dd>
 <dt><tt>tsmp</tt>
 <dd>The number of timestamps determined during the most recent
 minute bursts were received.</dd>
-
 </dl>
 
+<h4>Modes</h4>
+
+<p>The <tt>mode</tt> keyword of the <tt>server</tt> configuration
+command specifies the ICOM ID select code. A missing or zero argument
+disables the CI-V interface. Following are the ID select codes for the
+known radios.
+
+<p><table cols=6 width=100%>
+
+<tr>
+<td>Radio</td>
+<td>Hex</td>
+<td>Decimal</td>
+<td>Radio</td>
+<td>Hex</td>
+<td>Decimal</td>
+</tr>
+
+<tr>
+<td>IC725</td>
+<td>0x28</td>
+<td>40</td>
+<td>IC781</td>
+<td>0x26</td>
+<td>38</td>
+</tr>
+
+<tr>
+<td>IC726</td>
+<td>0x30</td>
+<td>48</td>
+<td>R7000</td>
+<td>0x08</td>
+<td>8</td>
+</tr>
+
+<tr>
+<td>IC735</td>
+<td>0x04</td>
+<td>4</td>
+<td>R71</td>
+<td>0x1A</td>
+<td>26</td>
+</tr>
+
+<tr>
+<td>IC751</td>
+<td>0x1c</td>
+<td>28</td>
+<td>R7100</td>
+<td>0x34</td>
+<td>52</td>
+</tr>
+
+<tr>
+<td>IC761</td>
+<td>0x1e</td>
+<td>30</td>
+<td>R72</td>
+<td>0x32</td>
+<td>50</td>
+</tr>
+
+<tr>
+<td>IC765</td>
+<td>0x2c</td>
+<td>44</td>
+<td>R8500</td>
+<td>0x4a</td>
+<td>74</td>
+</tr>
+
+<tr>
+<td>IC775</td>
+<td>0x46</td>
+<td>68</td>
+<td>R9000</td>
+<td>0x2a</td>
+<td>42</td>
+</tr>
+
+</table>
+
 <h4>Fudge Factors</h4>
 
 <dl>
diff --git a/include/icom.h b/include/icom.h
new file mode 100644 (file)
index 0000000..76b0a8e
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Header file for ICOM radios
+ */
+#include "ntp_types.h"
+
+/*
+ * Common definitions
+ */
+#define P_ERMSG        0x1             /* trace bus error messages */
+#define P_TRACE 0x2            /* trace CI-V messges */
+#define RETRY  3               /* max packet retries */
+#define IBAUD  B1200           /* autotune port speed */
+
+/*
+ * Radio identifier codes
+ */
+#define IC1271 0x24
+#define IC1275 0x18
+#define IC271  0x20
+#define IC275  0x10
+#define IC375  0x12
+#define IC471  0x22
+#define IC475  0x14
+#define IC575  0x16
+#define IC725  0x28
+#define IC726  0x30
+#define IC735  0x04
+#define IC751  0x1c
+#define IC761  0x1e
+#define IC765  0x2c
+#define IC775  0x46
+#define IC781  0x26
+#define IC970  0x2e
+#define R7000  0x08
+#define R71    0x1a
+#define R7100  0x34
+#define R72    0x32
+#define R8500  0x4a
+#define R9000  0x2a
+
+/*
+ * CI-V frame codes
+ */
+#define PR     0xfe            /* preamble */
+#define TX     0xe0            /* controller address */
+#define FI     0xfd            /* end of message */
+#define ACK    0xfb            /* controller normal reply */
+#define NAK    0xfa            /* controller error reply */
+#define PAD    0xff            /* transmit padding */
+
+/*
+ * CI-V controller commands
+ */
+#define V_FREQT        0x00            /* freq set (transceive) */
+#define V_MODET        0x01            /* set mode (transceive) */
+#define V_RBAND        0x02            /* read band edge */
+#define V_RFREQ        0x03            /* read frequency */
+#define V_RMODE        0x04            /* read mode */
+#define V_SFREQ        0x05            /* set frequency */
+#define V_SMODE        0x06            /* set mode */
+#define V_SVFO 0x07            /* select vfo */
+#define V_SMEM 0x08            /* select channel/bank */
+#define V_WRITE        0x09            /* write channel */
+#define V_VFOM 0x0a            /* memory -> vfo */
+#define V_CLEAR        0x0b            /* clear channel */
+#define V_ROFFS        0x0c            /* read tx offset */
+#define V_SOFFS        0x0d            /* write tx offset */
+#define V_SCAN 0x0e            /* scan control */
+#define V_SPLIT        0x0f            /* split control */
+#define V_DIAL 0x10            /* set dial tuning step */
+#define V_ATTEN        0x11            /* set attenuator */
+#define V_SANT 0x12            /* select antenna */
+#define V_ANNC 0x13            /* announce control */
+#define V_WRCTL        0x14            /* write controls */
+#define V_RDCTL        0x15            /* read controls */
+#define V_TOGL 0x16            /* set switches */
+#define V_ASCII        0x17            /* send CW message */
+#define V_POWER        0x18            /* power control */
+#define V_RDID 0x19            /* read model ID */
+#define V_SETW 0x1a            /* read/write channel/bank data */
+#define V_CTRL 0x7f            /* miscellaneous control */
+
+/*
+ * Function prototypes
+ */
+int    icom_init               P((char *, int));
+int    icom_freq               P((int, double));
index 6615be904e991b986ea818a2eb2a3b72694a18f2..330b3cc6732e13c65f25c7c757c863267c870bc6 100644 (file)
@@ -11,7 +11,8 @@ libntp_a_SOURCES = a_md5encrypt.c adjtime.c atoint.c atolfp.c atouint.c \
        msyslog.c netof.c numtoa.c numtohost.c octtoint.c prettydate.c \
        ranny.c refnumtoa.c statestr.c syssignal.c systime.c tsftomsu.c \
        tstotv.c tvtoa.c tvtots.c uglydate.c uinttoa.c utvtoa.c ymd2yd.c \
-       mfp_mul.c binio.c ieee754io.c gpstolfp.c recvbuff.c iosignal.c
+       mfp_mul.c binio.c ieee754io.c gpstolfp.c recvbuff.c iosignal.c \
+       icom.c
 libntp_a_LIBADD = @LIBOBJS@
 libntp_a_DEPENDENCIES = @LIBOBJS@
 INCLUDES = -I$(top_srcdir)/include
index e66749f1e2de30b83835ee0e2575f5262dae1aef..766c8a277f79034959513774bb0c6951a3d4e74f 100644 (file)
@@ -113,7 +113,8 @@ libntp_a_SOURCES = a_md5encrypt.c adjtime.c atoint.c atolfp.c atouint.c \
        msyslog.c netof.c numtoa.c numtohost.c octtoint.c prettydate.c \
        ranny.c refnumtoa.c statestr.c syssignal.c systime.c tsftomsu.c \
        tstotv.c tvtoa.c tvtots.c uglydate.c uinttoa.c utvtoa.c ymd2yd.c \
-       mfp_mul.c binio.c ieee754io.c gpstolfp.c recvbuff.c iosignal.c
+       mfp_mul.c binio.c ieee754io.c gpstolfp.c recvbuff.c iosignal.c \
+       icom.c
 
 libntp_a_LIBADD = @LIBOBJS@
 libntp_a_DEPENDENCIES = @LIBOBJS@
@@ -147,7 +148,7 @@ msutotsf$U.o msyslog$U.o netof$U.o numtoa$U.o numtohost$U.o \
 octtoint$U.o prettydate$U.o ranny$U.o refnumtoa$U.o statestr$U.o \
 syssignal$U.o systime$U.o tsftomsu$U.o tstotv$U.o tvtoa$U.o tvtots$U.o \
 uglydate$U.o uinttoa$U.o utvtoa$U.o ymd2yd$U.o mfp_mul$U.o binio$U.o \
-ieee754io$U.o gpstolfp$U.o recvbuff$U.o iosignal$U.o
+ieee754io$U.o gpstolfp$U.o recvbuff$U.o iosignal$U.o icom$U.o
 libntp_a_OBJECTS =  $(am_libntp_a_OBJECTS)
 AR = ar
 COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
@@ -176,7 +177,7 @@ $(DEPDIR)/decodenetnum$U.Po $(DEPDIR)/dofptoa$U.Po \
 $(DEPDIR)/dolfptoa$U.Po $(DEPDIR)/emalloc$U.Po \
 $(DEPDIR)/findconfig$U.Po $(DEPDIR)/fptoa$U.Po $(DEPDIR)/fptoms$U.Po \
 $(DEPDIR)/getopt$U.Po $(DEPDIR)/gpstolfp$U.Po $(DEPDIR)/hextoint$U.Po \
-$(DEPDIR)/hextolfp$U.Po $(DEPDIR)/humandate$U.Po \
+$(DEPDIR)/hextolfp$U.Po $(DEPDIR)/humandate$U.Po $(DEPDIR)/icom$U.Po \
 $(DEPDIR)/ieee754io$U.Po $(DEPDIR)/inttoa$U.Po $(DEPDIR)/iosignal$U.Po \
 $(DEPDIR)/lib_strbuf$U.Po $(DEPDIR)/machines$U.Po $(DEPDIR)/md5c$U.Po \
 $(DEPDIR)/memmove$U.Po $(DEPDIR)/mfp_mul$U.Po $(DEPDIR)/mfptoa$U.Po \
@@ -301,6 +302,8 @@ hextolfp_.c: hextolfp.c $(ANSI2KNR)
        $(CPP) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/hextolfp.c; then echo $(srcdir)/hextolfp.c; else echo hextolfp.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > hextolfp_.c
 humandate_.c: humandate.c $(ANSI2KNR)
        $(CPP) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/humandate.c; then echo $(srcdir)/humandate.c; else echo humandate.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > humandate_.c
+icom_.c: icom.c $(ANSI2KNR)
+       $(CPP) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/icom.c; then echo $(srcdir)/icom.c; else echo icom.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > icom_.c
 ieee754io_.c: ieee754io.c $(ANSI2KNR)
        $(CPP) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ieee754io.c; then echo $(srcdir)/ieee754io.c; else echo ieee754io.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > ieee754io_.c
 inttoa_.c: inttoa.c $(ANSI2KNR)
@@ -376,13 +379,13 @@ authencrypt_.o authkeys_.o authparity_.o authreadkeys_.o authusekey_.o \
 binio_.o buftvtots_.o caljulian_.o calleapwhen_.o caltontp_.o \
 calyearstart_.o clocktime_.o clocktypes_.o decodenetnum_.o dofptoa_.o \
 dolfptoa_.o emalloc_.o findconfig_.o fptoa_.o fptoms_.o getopt_.o \
-gpstolfp_.o hextoint_.o hextolfp_.o humandate_.o ieee754io_.o inttoa_.o \
-iosignal_.o lib_strbuf_.o machines_.o md5c_.o memmove_.o mfp_mul_.o \
-mfptoa_.o mfptoms_.o mktime_.o modetoa_.o mstolfp_.o msutotsf_.o \
-msyslog_.o netof_.o numtoa_.o numtohost_.o octtoint_.o prettydate_.o \
-ranny_.o recvbuff_.o refnumtoa_.o statestr_.o strerror_.o syssignal_.o \
-systime_.o tsftomsu_.o tstotv_.o tvtoa_.o tvtots_.o uglydate_.o \
-uinttoa_.o utvtoa_.o ymd2yd_.o : $(ANSI2KNR)
+gpstolfp_.o hextoint_.o hextolfp_.o humandate_.o icom_.o ieee754io_.o \
+inttoa_.o iosignal_.o lib_strbuf_.o machines_.o md5c_.o memmove_.o \
+mfp_mul_.o mfptoa_.o mfptoms_.o mktime_.o modetoa_.o mstolfp_.o \
+msutotsf_.o msyslog_.o netof_.o numtoa_.o numtohost_.o octtoint_.o \
+prettydate_.o ranny_.o recvbuff_.o refnumtoa_.o statestr_.o strerror_.o \
+syssignal_.o systime_.o tsftomsu_.o tstotv_.o tvtoa_.o tvtots_.o \
+uglydate_.o uinttoa_.o utvtoa_.o ymd2yd_.o : $(ANSI2KNR)
 
 tags: TAGS
 
@@ -461,6 +464,7 @@ distdir: $(DISTFILES)
 @AMDEP@include $(DEPDIR)/hextoint$U.Po
 @AMDEP@include $(DEPDIR)/hextolfp$U.Po
 @AMDEP@include $(DEPDIR)/humandate$U.Po
+@AMDEP@include $(DEPDIR)/icom$U.Po
 @AMDEP@include $(DEPDIR)/ieee754io$U.Po
 @AMDEP@include $(DEPDIR)/inttoa$U.Po
 @AMDEP@include $(DEPDIR)/iosignal$U.Po
diff --git a/libntp/icom.c b/libntp/icom.c
new file mode 100644 (file)
index 0000000..80a1432
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Program to control ICOM radios
+ *
+ * This is a ripoff of the utility routines in the ICOM software
+ * distribution. The only function provided is to load the radio
+ * frequency. All other parameters must be manually set before use.
+ */
+#include "icom.h"
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/termios.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/*
+ * Scraps
+ */
+#define BMAX 50                        /* max command length */
+#define DICOM /dev/icom/       /* ICOM port link */
+
+/*
+ * FSA definitions
+ */
+#define S_IDLE 0               /* idle */
+#define S_HDR  1               /* header */
+#define S_TX   2               /* address */
+#define S_DATA 3               /* data */
+#define S_ERROR        4               /* error */
+
+/*
+ * Local function prototypes
+ */
+static void doublefreq         P((double, u_char *, int));
+static int sndpkt              P((int, u_char *, u_char *));
+static int sndoctet            P((int));
+static int rcvoctet            P((void));
+
+/*
+ * Local variables
+ */
+static int flags;              /* trace flags */
+static int fd_icom;            /* radio file descriptor */
+static int state;              /* fsa state */
+
+/*
+ * icom_freq(radio, freq) - load radio frequency
+ */
+int
+icom_freq(                     /* returns 0 (ok), EIO (error) */
+       int ident,              /* ICOM radio identifier */
+       double freq             /* frequency (MHz) */
+       )
+{
+       u_char cmd[BMAX], rsp[BMAX];
+       int temp;
+       cmd[0] = V_SFREQ;
+       if (ident == IC735)
+               temp = 4;
+       else
+               temp = 5;
+       doublefreq(freq * 1e6, &cmd[1], temp);
+       temp = sndpkt(ident, cmd, rsp);
+       if (temp < 1 || rsp[0] != ACK)
+               return (EIO);
+       return (0);
+}
+
+/*
+ * doublefreq(freq, y, len) - double to ICOM frequency with padding
+ */
+void
+doublefreq(                    /* returns void */
+       double freq,            /* frequency */
+       u_char *x,              /* radio frequency */
+       int len                 /* length (octets) */
+       )
+{
+       int i;
+       char s1[11];
+       char *y;
+
+       sprintf(s1, " %10.0f", freq);
+       y = s1 + 10;
+       i = 0;
+       while (*y != ' ') {
+               x[i] = *y-- & 0x0f;
+               x[i] = x[i] | ((*y-- & 0x0f) << 4);
+               i++;
+       }
+       for (; i < len; i++)
+               x[i] = 0;
+       x[i] = FI;
+}
+
+/*
+ * Packet routines
+ *
+ * These routines send a packet and receive the response. If an error
+ * (collision) occurs on transmit, the packet is resent. If an error
+ * occurs on receive (timeout), all input to the terminating FI is
+ * discarded and the packet is resent. If the maximum number of retries
+ * is not exceeded, the program returns the number of octets in the user
+ * buffer; otherwise, it returns zero.
+ *
+ * ICOM frame format
+ *
+ * Frames begin with a two-octet preamble PR-PR followyd by the
+ * transceiver address RE, controller address TX, control code CN, zero
+ * or more data octets DA (depending on command), and terminator FI.
+ * Since the bus is bidirectional, every octet output is echoed on
+ * input. Every valid frame sent is answered with a frame in the same
+ * format, but with the RE and TX fields interchanged. The CN field is
+ * set to NAK if an error has occurred. Otherwise, the data are returned
+ * in this and following DA octets. If no data are returned, the CN
+ * octet is set to ACK.
+ *
+ *     +------+------+------+------+------+--//--+------+
+ *     |  PR  |  PR  |  RE  |  TX  |  CN  |  DA  |  FI  |
+ *     +------+------+------+------+------+--//--+------+
+ */
+/*
+ * initpkt() - initialize serial interface
+ *
+ * This routine opens the serial interface for raw transmission; that
+ * is, character-at-a-time, no stripping, checking or monkeying with the
+ * bits. For Unix, an input operation ends either with the receipt of a
+ * character or a 0.5-s timeout.
+ */
+int
+icom_init(
+       char *device,                   /* device name/link */
+       int trace                       /* trace flags */       )
+{
+       struct termios ttyb, *ttyp;
+
+       flags = trace;
+       ttyp = &ttyb;
+       if ((fd_icom = open(device, O_RDWR, 0777)) < 0)
+               return (fd_icom);
+       tcgetattr(fd_icom, ttyp);
+       ttyp->c_iflag = 0;      /* input modes */
+       ttyp->c_oflag = 0;      /* output modes */
+       ttyp->c_cflag = IBAUD|CS8|CREAD|CLOCAL; /* control modes */
+       ttyp->c_lflag = 0;      /* local modes */
+       ttyp->c_cc[VMIN] = 0;   /* min chars */
+       ttyp->c_cc[VTIME] = 5;  /* receive timeout */
+       tcsetattr(fd_icom, TCSANOW, ttyp);
+       return (0);
+}
+
+
+/*
+ * sndpkt(r, x, y) - send packet and receive response
+ *
+ * This routine sends a command frame, which consists of all except the
+ * preamble octets PR-PR. It then listens for the response frame and
+ * returns the payload to the caller. The routine checks for correct
+ * response header format; that is, the length of the response vector
+ * returned to the caller must be at least 2 and the RE and TX octets
+ * must be interchanged; otherwise, the operation is retried up to
+ * the number of times specified in a global variable.
+ *
+ * The trace function, which is enabled by the P_TRACE bit of the global
+ * flags variable, prints all characters received or echoed on the bus
+ * preceded by a T (transmit) or R (receive). The P_ERMSG bit of the
+ * flags variable enables printing of bus error messages.
+ *
+ * Note that the first octet sent is a PAD in order to allow time for
+ * the radio to flush its receive buffer after sending the previous
+ * response. Even with this precaution, some of the older radios
+ * occasionally fail to receive a command and it has to be sent again.
+ */
+int
+sndpkt(                                /* returns octet count */
+       int r,                  /* radio address */
+       u_char *cmd,            /* command vector */
+       u_char *rsp             /* response vector */
+       )
+{
+       int i, j, temp;
+
+       (void)tcflush(fd_icom, TCIOFLUSH);
+       for (i = 0; i < RETRY; i++) {
+               state = S_IDLE;
+
+               /*
+                * Transmit packet.
+                */
+               if (flags & P_TRACE)
+                       printf("icom T:");
+               sndoctet(PAD);  /* send header */
+               sndoctet(PR);
+               sndoctet(PR);
+               sndoctet(r);
+               sndoctet(TX);
+               for (j = 0; j < BMAX; j++) { /* send body */
+                       if (sndoctet(cmd[j]) == FI)
+                               break;
+               }
+               while (rcvoctet() != FI); /* purge echos */
+               if (cmd[0] == V_FREQT || cmd[0] == V_MODET)
+                       return(0);      /* shortcut for broadcast */
+
+               /*
+                * Receive packet. First, delete all characters
+                * preceeding a PR, then discard all PRs. Check that the
+                * RE and TX fields are correctly interchanged, then
+                * copy the remaining data and FI to the user buffer.
+                */
+               if (flags & P_TRACE)
+                       printf("\nicom R:");
+               j = 0;
+               while ((temp = rcvoctet()) != FI) {
+                       switch (state) {
+
+                       case S_IDLE:
+                               if (temp != PR)
+                                       continue;
+                               state = S_HDR;
+                               break;
+
+                       case S_HDR:
+                               if (temp == PR) {
+                                       continue;
+                               } else if (temp != TX) {
+                                       if (flags & P_ERMSG)
+                                               printf(
+                                                   "icom: TX error\n");
+                                       state = S_ERROR;
+                               }
+                               state = S_TX;
+                               break;
+
+                       case S_TX:
+                               if (temp != r) {
+                                       if (flags & P_ERMSG)
+                                               printf(
+                                                   "icom: RE error\n");
+                                       state = S_ERROR;
+                               }
+                               state = S_DATA;
+                               break;
+
+                       case S_DATA:
+                               if (j >= BMAX ) {
+                                       if (flags & P_ERMSG)
+                                               printf(
+                                           "icom: buffer overrun\n");
+                                       state = S_ERROR;
+                                       j = 0;
+                               }
+                               rsp[j++] = (u_char)temp;
+                               break;
+
+                       case S_ERROR:
+                               break;
+                       }
+               }
+               if (flags & P_TRACE)
+                       printf("\n");
+               if (j > 0) {
+                       rsp[j++] = FI;
+                       return(j);
+               }
+       }
+       if (flags & P_ERMSG)
+               printf("ciom: retries exceeded\n");
+       return(0);
+}
+
+/*
+ * Interface routines
+ *
+ * These routines read and write octets on the bus. In case of receive
+ * timeout a FI code is returned. In case of output collision (echo
+ * does not match octet sent), the remainder of the collision frame
+ * (including the trailing FI) is discarded.
+ */
+/*
+ * sndoctet(x) - send octet
+ */
+static int
+sndoctet(                      /* returns octet */
+       int x                   /* octet */
+       )
+{
+       u_char y;
+
+       y = (u_char)x;
+       write(fd_icom, &y, 1);
+       return (x);
+}
+
+/*
+ * rcvoctet () - receive octet
+ */
+static int
+rcvoctet()                     /* returns octet */
+{
+       u_char y;
+
+       if (read(fd_icom, &y, 1) < 1)
+               y = FI;         /* come here if timeout */
+       if (flags & P_TRACE && y != PAD)
+               printf(" %02x", y);
+       return (y);
+}
+
+/* end program */
index 4f3eecf4587c6f78821b683c46dbd22252500144..730f6ab8c2a62a844983a6d86989290350d14ea1 100644 (file)
 #include "ntp_calendar.h"
 #include "ntp_stdlib.h"
 
+#define ICOM   1               /* undefine to suppress ICOM code */
+
+#ifdef ICOM
+#include "icom.h"
+#endif /* ICOM */
+
 /*
  * Audio CHU demodulator/decoder
  *
@@ -37,8 +43,8 @@
  * 7335 kHz and 14670 kHz in upper sideband, compatible AM mode. An
  * ordinary shortwave receiver can be tuned manually to one of these
  * frequencies or, in the case of ICOM receivers, the receiver can be
- * tuned automatically using the minimuf and icom programs as
- * propagation conditions change throughout the day and night.
+ * tuned automatically using this program as propagation conditions
+ * change throughout the day and night.
  *
  * The driver receives, demodulates and decodes the radio signals when
  * connected to the audio codec of a Sun workstation running SunOS or
  * input port, where 0 is the mike port (default) and 1 is the line-in
  * port. It does not seem useful to select the compact disc player port.
  * Fudge flag3 enables audio monitoring of the input signal. For this
- * purpose, the speaker volume must be set before the driver is started. 
+ * purpose, the speaker volume must be set before the driver is started.
+ *
+ * The ICOM code is normally compiled in the driver. It isn't used,
+ * unless the mode keyword on the server configuration command specifies
+ * a nonzero ICOM ID select code. The C-IV trace is turned on if the
+ * debug level is greater than one.
  */
 /*
  * Interface definitions
 #define        SPEED232        B300    /* uart speed (300 baud) */
 #define        PRECISION       (-10)   /* precision assumed (about 1 ms) */
 #define        REFID           "CHU"   /* reference ID */
+#ifdef ICOM
+#define DWELL          5       /* minutes before qsy */
+#endif /* ICOM */
 #ifdef AUDIO_CHU
 #define        DESCRIPTION     "CHU Modem Receiver" /* WRU */
 
 #define STAMP          0x0080  /* too few timestamps */
 #define INYEAR         0x0100  /* valid B frame */
 #define INSYNC         0x0200  /* clock synchronized */
+#define AUTOT          0x0400  /* enable autotune */
 
 /*
  * Alarm status bits (alarm)
@@ -269,6 +284,9 @@ struct chuunit {
        int     errflg;         /* error flags */
        int     status;         /* status bits */
        int     bufptr;         /* buffer index pointer */
+       int     chan;           /* frequency identifier */
+       char    ident[10];      /* transmitter frequency */
+       int     dwell;          /* dwell minutes at current frequency */
 
        /*
         * Character burst variables
@@ -283,7 +301,7 @@ struct chuunit {
        int     burstcnt;       /* format A bursts this minute */
 
        /*
-        * Foremat particulars
+        * Format particulars
         */
        int     leap;           /* leap/dut code */
        int     dut;            /* UTC1 correction */
@@ -354,11 +372,14 @@ static    void    chu_debug       P((void));
 static char hexchar[] = "0123456789abcdef_-=";
 #ifdef AUDIO_CHU
 #ifdef HAVE_SYS_AUDIOIO_H
-struct audio_device device;    /* audio device ident */
+struct audio_device device;    /* audio device ident */
 #endif /* HAVE_SYS_AUDIOIO_H */
 static struct audio_info info; /* audio device info */
-static int     chu_ctl_fd;     /* audio control file descriptor */
+static int chu_ctl_fd;         /* audio control file descriptor */
 #endif /* AUDIO_CHU */
+#ifdef ICOM
+static double qsy[] = {3.330, 7.335, 14.670, 0}; /* frequencies (MHz) */
+#endif /* ICOM */
 
 /*
  * Transfer vector
@@ -385,8 +406,11 @@ chu_start(
 {
        struct chuunit *up;
        struct refclockproc *pp;
-
        int     fd;             /* file descriptor */
+#ifdef ICOM
+       char    tbuf[80];       /* trace buffer */
+       int     temp;
+#endif /* ICOM */
 #ifdef AUDIO_CHU
        int     i;              /* index */
        double  step;           /* codec adjustment */
@@ -403,7 +427,7 @@ chu_start(
        char device[20];        /* device name */
 
        /*
-        * Open serial port. Use RAW line discipline (required).
+        * Open serial port in raw mode.
         */
        (void)sprintf(device, DEVICE, unit);
        if (!(fd = refclock_open(device, SPEED232, LDISC_RAW))) {
@@ -463,6 +487,30 @@ chu_start(
        }
        DTOLFP(1. / SECOND, &up->tick);
 #endif /* AUDIO_CHU */
+       strcpy(up->ident, "X");
+#ifdef ICOM
+       temp = 0;
+#ifdef DEBUG
+       if (debug > 1)
+               temp = P_TRACE;
+#endif
+       if (peer->ttl > 0) {
+               if (icom_init("/dev/icom", temp) >= 0)
+                       up->status |= AUTOT;
+       }
+       if (up->status & AUTOT) {
+               if (icom_freq(peer->ttl, qsy[up->chan]) == 0) {
+                       sprintf(up->ident, "%.1f",
+                           qsy[up->chan]); 
+                       sprintf(tbuf, "chu: QSY to %s MHz", up->ident);
+                       record_clock_stats(&peer->srcadr, tbuf);
+#ifdef DEBUG
+                       if (debug)
+                               printf("%s\n", tbuf);
+#endif
+               }
+       }
+#endif /* ICOM */
        return (1);
 }
 
@@ -1147,7 +1195,9 @@ chu_poll(
        char    synchar, qual, leapchar;
        int     minset;
        int     temp;
-
+#ifdef ICOM
+       char    tbuf[80];       /* trace buffer */
+#endif /* ICOM */
        pp = peer->procptr;
        up = (struct chuunit *)pp->unitptr;
        if (pp->coderecv == pp->codeproc)
@@ -1167,10 +1217,30 @@ chu_poll(
         * Don't mess with anything if nothing has been heard.
         */
        chu_burst(peer);
+#ifdef ICOM
+       if (up->burstcnt > 2) {
+               up->dwell = 0;
+       } else if (up->dwell < DWELL) {
+               up->dwell++;
+       } else if (up->status & AUTOT) {
+               up->dwell = 0;
+               up->chan++;
+               if (qsy[up->chan] == 0)
+                       up->chan = 0;
+               icom_freq(peer->ttl, qsy[up->chan]);
+               sprintf(up->ident, "%.3f", qsy[up->chan]); 
+               sprintf(tbuf, "chu: QSY to %s MHz", up->ident);
+               record_clock_stats(&peer->srcadr, tbuf);
+#ifdef DEBUG
+               if (debug)
+                       printf("%s\n", tbuf);
+#endif
+       }
+#endif /* ICOM */
        if (up->burstcnt == 0)
                return;
        temp = chu_major(peer);
-       if (temp > 0 && up->status & INYEAR)
+       if (up->status & INYEAR)
                up->status |= INSYNC;
        qual = 0;
        if (up->status & (BFRAME | AFRAME))
@@ -1193,16 +1263,16 @@ chu_poll(
        }
 #ifdef AUDIO_CHU
        sprintf(pp->a_lastcode,
-           "%c%1X %4d %3d %02d:%02d:%02d.000 %c%x %+d %d %d %d %d %d %d",
+           "%c%1X %4d %3d %02d:%02d:%02d.000 %c%x %+d %d %d %s %d %d %d %d",
            synchar, qual, pp->year, pp->day, pp->hour, pp->minute,
            pp->second, leapchar, up->dst, up->dut, minset, up->gain,
-           up->tai, up->burstcnt, up->mindist, up->ntstamp);
+           up->ident, up->tai, up->burstcnt, up->mindist, up->ntstamp);
 #else
        sprintf(pp->a_lastcode,
-           "%c%1X %4d %3d %02d:%02d:%02d.000 %c%x %+d %d %d %d %d %d",
+           "%c%1X %4d %3d %02d:%02d:%02d.000 %c%x %+d %d %d %s %d %d %d",
            synchar, qual, pp->year, pp->day, pp->hour, pp->minute,
-           pp->second, leapchar, up->dst, up->dut, minset, up->tai,
-           up->burstcnt, up->mindist, up->ntstamp);
+           pp->second, leapchar, up->dst, up->dut, minset,
+           up->ident, up->tai, up->burstcnt, up->mindist, up->ntstamp);
 #endif /* AUDIO_CHU */
        pp->lencode = strlen(pp->a_lastcode);
 
index 566db813979e2ae9c817f5ced1107816fbb30f06..947acfb2385b6a34b3823f45efae3c407136bdce 100644 (file)
 #include "ntp_calendar.h"
 #include "ntp_stdlib.h"
 
+#define ICOM   1               /* undefine to suppress ICOM code */
+
+#ifdef ICOM
+#include "icom.h"
+#endif /* ICOM */
+
 /*
  * Audio WWV/H demodulator/decoder
  *
@@ -35,9 +41,9 @@
  * CO, and WWVH in Kauai, HI. Transmikssions are made continuously on
  * 2.5, 5, 10, 15 and 20 MHz in AM mode. An ordinary shortwave receiver
  * can be tuned manually to one of these frequencies or, in the case of
- * ICOM receivers, the receiver can be tuned automatically using the
- * minimuf and icom programs as propagation conditions change throughout
- * the day and night.
+ * ICOM receivers, the receiver can be tuned automatically using this
+ * program as propagation conditions change throughout the day and
+ * night.
  *
  * The driver receives, demodulates and decodes the radio signals when
  * connected to the audio codec of a Sun workstation running SunOS or
  * in this report have been modified somewhat to improve performance
  * under weak signal conditions and to provide an automatic station
  * identification feature.
+ *
+ * The ICOM code is normally compiled in the driver. It isn't used,
+ * unless the mode keyword on the server configuration command specifies
+ * a nonzero ICOM ID select code. The C-IV trace is turned on if the
+ * debug level is greater than one.
  */
 /*
  * Interface definitions
@@ -67,6 +78,7 @@
 #define OFFSET         128     /* companded sample offset */
 #define SIZE           256     /* decompanding table size */
 #define        MAXSIG          6000.   /* maximum signal level reference */
+#define MAXSNR         30.     /* max SNR reference */
 #define DGAIN          20.     /* data channel gain reference */
 #define SGAIN          10.     /* sync channel gain reference */
 #define MAXFREQ                (125e-6 * SECOND) /* freq tolerance (.0125%) */
@@ -75,7 +87,7 @@
 #define SYNSIZ         (800 * MS) /* minute sync matched filter size */
 #define UTCYEAR                72      /* the first UTC year */
 #define MAXERR         30      /* max data bit errors in minute */
-#define PANIC          (4 * 1440) /* panic restart */
+#define NCHAN          5       /* number of channels */
 
 /*
  * Macroni
 /*
  * General purpose status bits (status)
  *
- * Notes: WWV and/or WWVH are set when the minute sync pulse from either
- * or both stations has been acquired. SSYNC or MSYNC is set when the
- * second or minute sync pulse has been acquired, respectively. These
- * bits are cleared and the SYNERR alarm raised when the associated
- * pulse amplitude decays below STHR and ITHR, respectively. DGATE is
- * set if a data bit is invalid, BGATE is set if a BCD digit bit is
- * invalid. DSYNC is set when the minutes unit digit has reached the
- * threshold and INSYNC is set when if all nine digits have reached the
- * threshold.
- *
- * The data error counter is incremented for each invalid data bit. If
- * too many data bit errors are encountered in one minute, the MODERR
- * alarm is raised. The DECERR alarm is raised if a maximum likelihood
- * digit fails to compare with the current clock digit. If the
- * probability of any miscellaneous bit or any digit falls below ETHR,
- * the SYMERR alarm is raised.
+ * Notes: SELV and/or SELH are set when the minute sync pulse from
+ * either or both WWV and/or WWVH stations has been heard. MSYNC is set
+ * when the minute sync pulse has been acquired and never reset. SSYNC
+ * is set when the second sync pulse has been acquired and cleared by
+ * watchdog or signal loss. DSYNC is set when the minutes unit digit has
+ * reached the threshold and INSYNC is set when if all nine digits have
+ * reached the threshold and never cleared.
  *
- * LEPSEC is set when the SECWAR of the timecode is set on the last
- * second of 30 June or 31 December. At the end of this minute both the
- * receiver and transmitter insert second 60 in the minute and the
- * minute sync slips a second.
+ * DGATE is set if a data bit is invalid, BGATE is set if a BCD digit
+ * bit is invalid. SFLAG is set when during seconds 59, 0 and 1 while
+ * probing for alternate frequencies. LEPSEC is set when the SECWAR of
+ * the timecode is set on the last second of 30 June or 31 December. At
+ * the end of this minute both the receiver and transmitter insert
+ * second 60 in the minute and the minute sync slips a second.
  */
 #define MSYNC          0x0001  /* minute epoch sync */
 #define SSYNC          0x0002  /* second epoch sync */
 #define INSYNC         0x0008  /* clock synchronized */
 #define DGATE          0x0010  /* data bit error */
 #define BGATE          0x0020  /* BCD digit bit error */
-#define WWV            0x0100  /* WWV station detected */
-#define SELV           0x0200  /* WWV station selected */
-#define WWVH           0x0400  /* WWVH station detected */
-#define SELH           0x0800  /* WWVH station selected */
-#define LEPSEC         0x1000  /* leap second in progress */
+#define SFLAG          0x1000  /* probe flag */
+#define LEPSEC         0x2000  /* leap second in progress */
+
+/*
+ * Station scoreboard bits (select)
+ *
+ * These are used to establish the signal quality for each of the five
+ * frequencies and two stations.
+ */
+#define DATANG         0x0001  /* data below threshold or SNR */
+#define SYNCNG         0x0002  /* sync below threshold or SNR */
+#define JITRNG         0x0004  /* jitter above threshold */
+#define SELV           0x0100  /* WWV station select */
+#define SELH           0x0200  /* WWVH station select */
 
 /*
  * Alarm status bits (alarm)
  * This bit can be set during the next minute if the associated alarm
  * condition is raised. This provides a way to remember alarm conditions
  * up to four minutes.
+ *
+ * If not tracking both minute sync and second sync, the SYNERR alarm is
+ * raised. The data error counter is incremented for each invalid data
+ * bit. If too many data bit errors are encountered in one minute, the
+ * MODERR alarm is raised. The DECERR alarm is raised if a maximum
+ * likelihood digit fails to compare with the current clock digit. If
+ * the probability of any miscellaneous bit or any digit falls below the
+ * threshold, the SYMERR alarm is raised.
  */
 #define DECERR         0       /* BCD digit compare error */
 #define SYMERR         4       /* low bit or digit probability */
 #define SYNERR         12      /* not synchronized to station */
 
 /*
- * Tone frequency definitions.
- */
-#define MS             8       /* samples per millisecond */
-#define IN100          1       /* 100 Hz 4.5-deg sin table */
-#define IN1000         10      /* 1000 Hz 4.5-deg sin table */
-#define IN1200         12      /* 1200 Hz 4.5-deg sin table */
-
-/*
- * Acquisition and tracking time constants. Usually powers of 2.
+ * Watchdog timeouts (watch)
+ *
+ * If these timeouts expire, the status bits are mashed to zero and the
+ * driver starts from scratch. Suitably more refined procedures may be
+ * developed in future. All these are in minutes.
  */
-#define MINAVG         8       /* min time constant (s) */
-#define MAXAVG         7       /* max time constant (log2 s) */
-#define TCONST         16      /* minute time constant (s) */
-#define SYNCTC         (1024 / (1 << MAXAVG)) /* FLL constant (s) */
+#define ACQSN          5       /* acquisition timeout */
+#define HSPEC          15      /* second sync timeout */
+#define DIGIT          20      /* minute unit digit timeout */
+#define CHQSY          15      /* channel timeout */
+#define PANIC          (4 * 1440) /* panic timeout */
 
 /*
  * Thresholds. These establish the minimum signal level, minimum SNR and
  */
 #define ATHR           2000    /* acquisition amplitude threshold */
 #define ASNR           6.0     /* acquisition SNR threshold (dB) */
-#define AWND           20      /* acquisition window threshold (ms) */
-#define ACMP           5       /* acquisition compare threshold */
+#define AWND           50      /* acquisition window threshold (ms) */
+#define AMIN           3       /* acquisition min compare count */
+#define AMAX           5       /* max compare count */
+#define QTHR           1000    /* QSY amplitude threshold */
+#define QSNR           10.0    /* QSY SNR threshold (dB) */
 #define STHR           500     /* second sync amplitude threshold */
 #define SCMP           10      /* second sync compare threshold */
 #define DTHR           1000    /* bit amplitude threshold */
 #define BSNR           3.0     /* digit likelihood threshold (dB) */
 #define BCMP           5       /* digit compare threshold (dB) */
 
+/*
+ * Tone frequency definitions.
+ */
+#define MS             8       /* samples per millisecond */
+#define IN100          1       /* 100 Hz 4.5-deg sin table */
+#define IN1000         10      /* 1000 Hz 4.5-deg sin table */
+#define IN1200         12      /* 1200 Hz 4.5-deg sin table */
+
+/*
+ * Acquisition and tracking time constants. Usually powers of 2.
+ */
+#define MINAVG         8       /* min time constant (s) */
+#define MAXAVG         7       /* max time constant (log2 s) */
+#define TCONST         16      /* minute time constant (s) */
+#define SYNCTC         (1024 / (1 << MAXAVG)) /* FLL constant (s) */
+
 /*
  * Miscellaneous status bits (misc)
  *
 #define SECWAR         0x40    /* 3 leap second warning */
 
 /*
- * The total system delay with the DSP93 program was is at 22.5 ms,
+ * The total system delay with the DSP93 program is at 22.5 ms,
  * including the propagation delay from Ft. Collins, CO, to Newark, DE
  * (8.9 ms), the communications receiver delay and the delay of the
  * DSP93 program itself. The DSP93 program delay is due mainly to the
@@ -258,8 +296,11 @@ struct progx {
 #define DECIM2         7       /* BCD digit 0-2 */
 #define MSCBIT         8       /* miscellaneous bit */
 #define MSC20          9       /* miscellaneous bit */         
-#define MIN1           10      /* minute */            
-#define MIN2           11      /* leap second */
+#define MSC21          10      /* QSY probe channel */         
+#define MIN1           11      /* minute */            
+#define MIN2           12      /* leap second */
+#define SYNC2          13      /* QSY data channel */          
+#define SYNC3          14      /* QSY data channel */          
 
 /*
  * Offsets in decoding matrix
@@ -270,8 +311,8 @@ struct progx {
 #define YR             7       /* year digits (2) */
 
 struct progx progx[] = {
-       {IDLE,  0},             /* 0 punched */
-       {IDLE,  0},             /* 1 */
+       {SYNC2, 0},             /* 0 latch sync max */
+       {SYNC3, 0},             /* 1 QSY data channel */
        {MSCBIT, DST2},         /* 2 dst2 */
        {MSCBIT, SECWAR},       /* 3 lw */
        {COEF,  0},             /* 4 1 year units */
@@ -328,8 +369,8 @@ struct progx progx[] = {
        {MSC20, DST1},          /* 55 dst1 */
        {MSCBIT, DUT1},         /* 56 0.1 dut */
        {MSCBIT, DUT2},         /* 57 0.2 */
-       {MSCBIT, DUT4},         /* 58 0.4 */
-       {MIN1,  0},             /* 59 p6 */
+       {MSC21, DUT4},          /* 58 0.4 QSY probe channel */
+       {MIN1,  0},             /* 59 p6 latch sync min */
        {MIN2,  0}              /* 60 leap second */
 };
 
@@ -438,22 +479,35 @@ struct decvec {
  * for WWVH. Other than frequency, the format is the same.
  */
 struct sync {
-       double  ibuf[SYNSIZ];   /* I channel delay line */
-       double  qbuf[SYNSIZ];   /* Q channel delay line */
-       double  iamp;           /* I channel amplitude */
-       double  qamp;           /* Q channel amplitude */
+       double  amp;            /* amplitude (square) */
+       double  synamp;         /* sync amplitude at 800 ms */
        double  noise;          /* max amplitude off pulse */
        double  max;            /* max amplitude on pulse */
        double  lastmax;        /* last max amplitude on pulse */
        long    pos;            /* position at maximum amplitude */
        long    lastpos;        /* last position at maximum amplitude */
+       long    jitter;         /* shake, wiggle and waggle */
        long    mepoch;         /* minute synch epoch */
        int     count;          /* compare counter */
-       int     sinptr;         /* sine table pointer */
-       int     incr;           /* sine table increment */
-       char    call[5];        /* station callsign */
-       int     ident;          /* station ident bit */
-       int     pdelay;         /* propagation delay (samples) */
+       char    refid[5];       /* reference identifier */
+       char    ident[4];       /* station identifier */
+       int     select;         /* select bits */
+       double  synmax;         /* max sync signal */
+       double  synmin;         /* min sync signal */
+       double  synsnr;         /* sync signal SNR */
+};
+
+/*
+ * The channel structure is used to mitigate between channels. At this
+ * point we have already decided which station to use.
+ */
+struct chan {
+       int     gain;           /* audio gain */
+       double  datmax;         /* max data signal after second 59 */
+       double  datmin;         /* min data signal after second 0 */
+       double  datsnr;         /* data SNR */
+       struct sync wwv;        /* wwv station */
+       struct sync wwvh;       /* wwvh station */
 };
 
 /*
@@ -465,6 +519,7 @@ struct wwvunit {
        double  comp[SIZE];     /* decompanding table */
        double  phase, freq;    /* logical clock phase and frequency */
        double  monitor;        /* audio monitor point */
+       int     fd_icom;        /* ICOM file descriptor */
        int     errflg;         /* error flags */
        int     bufcnt;         /* samples in buffer */
        int     bufptr;         /* buffer index pointer */
@@ -473,7 +528,8 @@ struct wwvunit {
        int     clipcnt;        /* sample clipped count */
        int     seccnt;         /* second countdown */
        int     minset;         /* minutes since last clock set */
-       char    call[5];        /* station callsign */
+       int     watch;          /* watchcat */
+       int     swatch;         /* second sync watchcat */
 
        /*
         * Variables used to establish basic system timing
@@ -491,16 +547,23 @@ struct wwvunit {
        int     rsec;           /* receiver seconds counter */
        long    mphase;         /* minute sample counter */
        long    nepoch;         /* minute epoch index */
-       int     count;          /* minute sync compare counter */
-       int     jptr;           /* minute sync pulse counter */
-       struct sync wwv;        /* WWV sync channel */
-       struct sync wwvh;       /* WWVH sync channel */
+
+       /*
+        * Variables used to mitigate which channel to use
+        */
+       struct chan mitig[NCHAN]; /* channel data */
+       struct sync *sptr;      /* station pointer */
+       int     dchan;          /* data channel */
+       int     schan;          /* probe channel */
+       int     achan;          /* active channel */
 
        /*
         * Variables used by the clock state machine
         */
        struct decvec decvec[9]; /* decoding matrix */
-       int     pdelay;         /* propagation delay */
+       int     cdelay;         /* WWV propagation delay (samples) */
+       int     hdelay;         /* WVVH propagation delay (samples) */
+       int     pdelay;         /* propagation delay (samples) */
        int     tphase;         /* transmitter sample counter */
        int     tsec;           /* transmitter seconds counter */
        int     digcnt;         /* count of digits synchronized */
@@ -509,16 +572,17 @@ struct wwvunit {
         * Variables used to estimate signal levels and bit/digit
         * probabilities
         */
-       double  datamp;         /* max data bit amplitude */
-       double  noiamp;         /* average noise amplitude */
-       double  datsnr;         /* data bit SNR (dB) */
+       double  datmax;         /* data signal at 200 ms (peak) */
+       double  sigamp;         /* I-channel peak signal amplitude */
+       double  noiamp;         /* I-channel average noise amplitude */
+       double  datsnr;         /* data SNR (dB) */
 
        /*
         * Variables used to establish status and alarm conditions
         */
        int     status;         /* status bits */
-       int     misc;           /* miscellaneous timecode bits */
        int     alarm;          /* alarm flashers */
+       int     misc;           /* miscellaneous timecode bits */
        int     errcnt;         /* data bit error counter */
 };
 
@@ -549,6 +613,9 @@ static      int     wwv_audio       P((void));
 static void    wwv_show        P((void));
 static double  wwv_snr         P((double, double));
 static int     carry           P((struct decvec *));
+static void    wwv_newchan     P((struct peer *));
+static void    wwv_qsy         P((struct peer *, int));
+static double qsy[NCHAN] = {2.5, 5, 10, 15, 20}; /* frequencies (MHz) */
 
 /*
  * Transfer vector
@@ -584,6 +651,10 @@ wwv_start(
 {
        struct refclockproc *pp;
        struct wwvunit *up;
+       struct chan *cp;
+#ifdef ICOM
+       int     temp;
+#endif /* ICOM */
 
        /*
         * Local variables
@@ -629,7 +700,6 @@ wwv_start(
        pp->clockdesc = DESCRIPTION;
        memcpy((char *)&pp->refid, REFID, 4);
        DTOLFP(1. / SECOND, &up->tick);
-       up->gain = (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) / 2;
        if (wwv_audio() < 0) {
                io_closeclock(&pp->io);
                return(0);
@@ -649,6 +719,11 @@ wwv_start(
                 if (i % 16 == 0)
                    step *= 2.;
        }
+
+       /*
+        * Initialize the decoding matrix with the radix for each digit
+        * position.
+        */
        up->decvec[MN].radix = 10;      /* minutes */
        up->decvec[MN + 1].radix = 6;
        up->decvec[HR].radix = 10;      /* hours */
@@ -658,13 +733,37 @@ wwv_start(
        up->decvec[DA + 2].radix = 4;
        up->decvec[YR].radix = 10;      /* years */
        up->decvec[YR + 1].radix = 10;
-       up->wwv.incr = IN1000;
-       up->wwv.ident = WWV | SELV;
-       strcpy(up->wwv.call, "WWV ");
-       up->wwvh.incr = IN1200;
-       up->wwvh.ident = WWVH | SELH;
-       strcpy(up->wwvh.call, "WWVH");
-       strcpy(up->call, "NONE");
+
+       /*
+        * Initialize the station processes for audio gain, select bit,
+        * station/frequency identifier and reference identifier.
+        */
+       up->gain = (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) / 2;
+       for (i = 0; i < NCHAN; i++) {
+               cp = &up->mitig[i];
+               cp->gain = up->gain;
+               cp->wwv.select = SELV;
+               strcpy(cp->wwv.refid, "WWV ");
+               sprintf(cp->wwv.ident,"C%.0f", floor(qsy[i]));
+               cp->wwvh.select = SELH;
+               strcpy(cp->wwvh.refid, "WWVH");
+               sprintf(cp->wwvh.ident, "H%.0f", floor(qsy[i]));
+       }
+
+       /*
+        * Initialize autotune if available. Start out at 15 MHz.
+        */
+#ifdef ICOM
+       temp = 0;
+#ifdef DEBUG
+       if (debug > 1)
+               temp = P_TRACE;
+#endif
+       if (peer->ttl != 0)
+               up->fd_icom = icom_init("/dev/icom", temp);
+#endif /* ICOM */
+       up->schan = 3;
+       wwv_qsy(peer, up->schan);
        return (1);
 }
 
@@ -768,18 +867,14 @@ wwv_receive(
                 * receiver phase delay, mostly due to the 600-Hz
                 * IIR bandpass filter used for the sync signals.
                 */
-               up->wwv.pdelay = (int)(SECOND * (pp->fudgetime1 +
-                   PDELAY));
-               up->wwvh.pdelay = (int)(SECOND * (pp->fudgetime2 +
-                   PDELAY));
+               up->cdelay = (int)(SECOND * (pp->fudgetime1 + PDELAY));
+               up->hdelay = (int)(SECOND * (pp->fudgetime2 + PDELAY));
                up->seccnt = (up->seccnt + 1) % SECOND;
                if (up->seccnt == 0) {
                        if (pp->sloppyclockflag & CLK_FLAG2)
                            up->port = AUDIO_LINE_IN;
                        else
                            up->port = AUDIO_MICROPHONE;
-                       wwv_gain(peer);
-                       up->clipcnt = 0;
                }
 
                /*
@@ -827,7 +922,8 @@ wwv_receive(
  *
  * This routine keeps track of status. If nothing is heard for two
  * successive poll intervals, a timeout event is declared and any
- * orphaned timecode updates are sent to foster care. 
+ * orphaned timecode updates are sent to foster care. Once the clock is
+ * set, it always appears reachable, unless reset by watchdog timeout.
  */
 static void
 wwv_poll(
@@ -844,12 +940,8 @@ wwv_poll(
                up->errflg = CEVNT_TIMEOUT;
        else
                pp->polls++;
-       if (up->status & INSYNC) {
-               if (up->minset > PANIC)
-                       up->status = up->alarm = 0;
-               else
-                       peer->reach |= 1;
-       }
+       if (up->status & INSYNC)
+               peer->reach |= 1;
        if (up->errflg)
                refclock_report(peer, up->errflg);
        up->errflg = 0;
@@ -857,7 +949,7 @@ wwv_poll(
 
 
 /*
- * wwv_epoch() main loop
+ * wwv_epoch -  main loop
  *
  * This routine establishes receiver and transmitter epoch
  * synchronization and determines the data subcarrier pulse length.
@@ -887,10 +979,10 @@ wwv_epoch(
        static double dpulse;   /* data pulse length */
        struct refclockproc *pp;
        struct wwvunit *up;
+       struct sync *sp;
        l_fp offset;            /* NTP format offset */
        char tbuf[80];          /* monitor buffer */
        double dtemp, etemp;
-       int temp;
 
        /*
         * Extract the data and sync signals.
@@ -901,45 +993,65 @@ wwv_epoch(
 
        /*
         * Estimate the noise level by integrating the I-channel energy
-        * before epoch 30 ms.
+        * at epoch 30 ms when not probing.
         */
-       if (up->rphase < 30 * MS) {
+       if (up->rphase == 30 * MS && !(up->status & SFLAG)) {
                up->noiamp += (up->irig - up->noiamp) / (MINAVG <<
                    up->avgint);
 
        /*
-        * Strobe the maximum I-channel data signal at epoch 200 ms.
+        * Strobe the peak I-channel data signal at epoch 200 ms.
         * Compute the SNR and adjust the 100-Hz reference oscillator
-        * phase using the Q-channel data signal at that epoch.
+        * phase using the Q-channel data signal at that epoch. Save the
+        * envelope amplitude for the probe channel, but don't jiggle
+        * the subcarrier phase during the probe.
         */
        } else if (up->rphase == 200 * MS) {
-               up->datamp = up->irig;
-               if (up->datamp < 0)
-                       up->datamp = 0;
-               up->datsnr = wwv_snr(up->datamp, up->noiamp);
-               up->datpha = up->qrig / (MINAVG << up->avgint);
-               if (up->datpha >= 0) {
-                       up->datapt++;
-                       if (up->datapt >= 80)
-                               up->datapt -= 80;
+               if (up->status & SFLAG) {
+                       up->datmax = sqrt(up->irig * up->irig +
+                           up->qrig * up->qrig);
                } else {
-                       up->datapt--;
-                       if (up->datapt < 0)
-                               up->datapt += 80;
+                       up->sigamp = up->irig;
+                       if (up->sigamp < 0)
+                               up->sigamp = 0;
+                       up->datsnr = wwv_snr(up->sigamp, up->noiamp);
+                       up->datpha = up->qrig / (MINAVG << up->avgint);
+                       if (up->datpha >= 0) {
+                               up->datapt++;
+                               if (up->datapt >= 80)
+                                       up->datapt -= 80;
+                       } else {
+                               up->datapt--;
+                               if (up->datapt < 0)
+                                       up->datapt += 80;
+                       }
                }
 
        /*
-        * The slice level is set half way between the noise level and
-        * the maximum signal. Strobe the negative zero crossing after
-        * epoch 200 ms and record the epoch at that time. This defines
-        * the size of the data pulse, which will later be converted
-        * into scaled bit probabilities. However, the data
+        * The slice level is set half way between the peak signal and
+        * noise levels. Strobe the negative zero crossing after epoch
+        * 200 ms and record the epoch at that time. This defines the
+        * length of the data pulse, which will later be converted into
+        * scaled bit probabilities. At epoch 800 ms, save the WWV and
+        * WWVH minute pulse envelope amplitudes for the probe channel.
         */
        } else if (up->rphase > 200 * MS) {
-               dtemp = (up->datamp + up->noiamp) / 2;
+               dtemp = (up->sigamp + up->noiamp) / 2;
                if (up->irig < dtemp && dpulse == 0)
                        dpulse = up->rphase;
-       }
+
+               /*
+                * Same the minute sync pulse amplitude at epoch 800 for
+                * both the WWV and WWVH stations. This will be used
+                * later for channel mitigation.
+                */
+               if (up->rphase == 800 * MS) {
+                       sp = &up->mitig[up->achan].wwv;
+                       sp->synamp = sqrt(sp->amp);
+                       sp = &up->mitig[up->achan].wwvh;
+                       sp->synamp = sqrt(sp->amp);
+               }
+       }
 
        /*
         * At the end of the transmitter second, crank the clock state
@@ -955,10 +1067,10 @@ wwv_epoch(
 
                /*
                 * Determine the current offset from the time of century
-                * and the sample timestamp, but only if the clock has
-                * been set and the second sync is tracking.
+                * and the sample timestamp, but only if no alarms have
+                * been raised in the present or previous minute.
                 */
-               if (up->status & INSYNC && up->status & SSYNC) {
+               if (up->status & INSYNC && (up->alarm & 0x3333) == 0) {
                        pp->second = up->tsec;
                        pp->minute = up->decvec[MN].digit +
                            up->decvec[MN + 1].digit * 10;
@@ -973,6 +1085,12 @@ wwv_epoch(
                                pp->year += 2000;
                        else
                                pp->year += 1900;
+
+                       /*
+                        * We have to simulate refclock_process() here,
+                        * since the fudgetime gets added much earlier
+                        * than this.
+                        */
                        pp->lastrec = up->timestamp;
                        L_CLR(&offset);
                        if (!clocktime(pp->day, pp->hour, pp->minute,
@@ -982,14 +1100,6 @@ wwv_epoch(
                        else
                                refclock_process_offset(pp, offset,
                                    pp->lastrec, 0.);
-                       if (up->misc & SECWAR)
-                               pp->leap = LEAP_ADDSECOND;
-                       else
-                               pp->leap = LEAP_NOWARNING;
-                       if (!refclock_process(pp))
-                               up->errflg = CEVNT_BADTIME;
-               } else {
-                       pp->leap = LEAP_NOTINSYNC;
                }
        }
 
@@ -999,14 +1109,13 @@ wwv_epoch(
         */
        up->rphase++;
        if (up->epoch == up->repoch) {
-               temp = up->rsec;
                dtemp = wwv_data(up, dpulse);
                etemp = wwv_rsec(peer, dtemp);
                if (up->status & MSYNC && !(up->status & DSYNC)) {
                        sprintf(tbuf,
-                           "wwv3 %2d %04x %5.0f %04x %5.0f %5.0f %5.1f %5.0f %5.0f",
-                           up->rsec, up->status, up->epomax, up->alarm,
-                           up->datamp, up->datpha, up->datsnr, dtemp,
+                   "wwv3 %2d %04x %5.0f %5.0f %5.0f %5.1f %5.0f %5.0f",
+                           up->rsec, up->status, up->epomax,
+                           up->sigamp, up->datpha, up->datsnr, dtemp,
                            etemp);
                        if (pp->sloppyclockflag & CLK_FLAG4)
                                record_clock_stats(&peer->srcadr, tbuf);
@@ -1015,13 +1124,14 @@ wwv_epoch(
                                printf("%s\n", tbuf);
 #endif /* DEBUG */
                }
+               wwv_gain(peer);
                up->rphase = dpulse = 0;
        }
 }
 
 
 /*
- * wwv_rf() - process signals and demodulate to baseband
+ * wwv_rf - process signals and demodulate to baseband
  *
  * This routine grooms and filters decompanded raw audio samples. The
  * output signals include the 100-Hz baseband data signal in quadrature
@@ -1075,9 +1185,21 @@ wwv_rf(
        static double mf[41];   /* 1000/1200-Hz mf delay line */
        double mfsync = 0;      /* mf output */
 
+       static int iptr;        /* data channel pointer */
        static double ibuf[DATSIZ]; /* data I channel delay line */
        static double qbuf[DATSIZ]; /* data Q channel delay line */
-       static int iptr;        /* data channels pointer */
+
+       static int jptr;        /* sync channel pointer */
+       static double cibuf[SYNSIZ]; /* wwv I channel delay line */
+       static double cqbuf[SYNSIZ]; /* wwv Q channel delay line */
+       static double ciamp;    /* wwv I channel amplitude */
+       static double cqamp;    /* wwv Q channel amplitude */
+       static int csinptr;     /* wwv channel phase */
+       static double hibuf[SYNSIZ]; /* wwvh I channel delay line */
+       static double hqbuf[SYNSIZ]; /* wwvh Q channel delay line */
+       static double hiamp;    /* wwvh I channel amplitude */
+       static double hqamp;    /* wwvh Q channel amplitude */
+       static int hsinptr;     /* wwvh channels phase */
 
        static double epobuf[SECOND]; /* epoch sync comb filter */
        static double epomax;   /* epoch sync amplitude buffer */
@@ -1086,6 +1208,7 @@ wwv_rf(
        static int iniflg;      /* initialization flag */
        struct sync *sp;
        double dtemp;
+       long ltemp;
        int i;
 
        pp = peer->procptr;
@@ -1097,6 +1220,10 @@ wwv_rf(
                memset((char *)mf, 0, sizeof(mf));
                memset((char *)ibuf, 0, sizeof(ibuf));
                memset((char *)qbuf, 0, sizeof(qbuf));
+               memset((char *)cibuf, 0, sizeof(cibuf));
+               memset((char *)cqbuf, 0, sizeof(cqbuf));
+               memset((char *)hibuf, 0, sizeof(hibuf));
+               memset((char *)hqbuf, 0, sizeof(hqbuf));
                memset((char *)epobuf, 0, sizeof(epobuf));
        }
        up->monitor = isig;             /* change for debug */
@@ -1129,13 +1256,13 @@ wwv_rf(
         * 170-ms synchronous matched filters to produce the amplitude
         * and phase signals used by the decoder.
         */
-       i = up->datapt;                 /* data I channel */
+       i = up->datapt;
        up->datapt = (up->datapt + IN100) % 80;
        dtemp = sintab[i] * data / DATSIZ * DGAIN;
        up->irig -= ibuf[iptr];
        ibuf[iptr] = dtemp;
        up->irig += dtemp;
-       i = (i + 20) % 80;              /* data Q channel */
+       i = (i + 20) % 80;
        dtemp = sintab[i] * data / DATSIZ * DGAIN;
        up->qrig -= qbuf[iptr];
        qbuf[iptr] = dtemp;
@@ -1178,62 +1305,116 @@ wwv_rf(
         * to synchronize the second and minute and to detect which one
         * (or both) the WWV or WWVH signal is present.
         */
-       wwv_qrz(peer, &up->wwv, syncx);
-       wwv_qrz(peer, &up->wwvh, syncx);
-       up->jptr = (up->jptr + 1) % SYNSIZ;
+       up->mphase = (up->mphase + 1) % MINUTE;
+
+       i = csinptr;
+       csinptr = (csinptr + IN1000) % 80;
+       dtemp = sintab[i] * syncx / SYNSIZ * SGAIN;
+       ciamp = ciamp - cibuf[jptr] + dtemp;
+       cibuf[jptr] = dtemp;
+       i = (i + 20) % 80;
+       dtemp = sintab[i] * syncx / SYNSIZ * SGAIN;
+       cqamp = cqamp - cqbuf[jptr] + dtemp;
+       cqbuf[jptr] = dtemp;
+       dtemp = ciamp * ciamp + cqamp * cqamp;
+       wwv_qrz(peer, &up->mitig[up->schan].wwv, dtemp);
+
+       i = hsinptr;
+       hsinptr = (hsinptr + IN1200) % 80;
+       dtemp = sintab[i] * syncx / SYNSIZ * SGAIN;
+       hiamp = hiamp - hibuf[jptr] + dtemp;
+       hibuf[jptr] = dtemp;
+       i = (i + 20) % 80;
+       dtemp = sintab[i] * syncx / SYNSIZ * SGAIN;
+       hqamp = hqamp - hqbuf[jptr] + dtemp;
+       hqbuf[jptr] = dtemp;
+       dtemp = hiamp * hiamp + hqamp * hqamp;
+       wwv_qrz(peer, &up->mitig[up->schan].wwvh, dtemp);
+
+       jptr = (jptr + 1) % SYNSIZ;
+
        if (up->mphase == 0) {
 
                /*
-                * The program listens for minute sync pulses from both
-                * WWV and WWVH. The station with the greater compare
-                * count is selected, with ties broken by WWV, but only
-                * if the count is at least two. Once a station has been
-                * selected, second sync pulses only from that station
-                * will be used.
-                *
-                * The leap second is ugly. Just skip it and set the
-                * minute epoch back one second.
+                * This section is called once per minute at the minute
+                * epoch independently of the transmitter or receiver
+                * minute. If the leap bit is set, set the minute epoch
+                * back one second so the station processes don't miss a
+                * beat. Then, increment the watchdog counter and test
+                * for two sets of conditions depending on whether
+                * minute sync has been acquired or not.
                 */
+               up->watch++;
                if (up->rsec == 60) {
                        up->mphase -= SECOND;
                        if (up->mphase < 0)
                                up->mphase += MINUTE;
-               } else {
-                       if (up->wwv.count >= up->wwvh.count)
-                               sp = &up->wwv;
+               } else if (!(up->status & MSYNC)) {
+                       up->alarm |= 1 << SYNERR;
+
+                       /*
+                        * If minute sync has not been acquired, the
+                        * program listens for minute sync pulses from
+                        * both WWV and WWVH. The station with the
+                        * greater compare count is selected, with ties
+                        * broken by WWV, but only if the count is at
+                        * least three. Once a station has been
+                        * acquired, it is initialized and begins
+                        * tracking the signal.
+                        */
+                       if (up->mitig[up->achan].wwv.count >=
+                           up->mitig[up->achan].wwvh.count)
+                               sp = &up->mitig[up->achan].wwv;
                        else
-                               sp = &up->wwvh;
-                       up->count = sp->count;
-                       if (up->count >= 2) {
-                               up->pdelay = sp->pdelay;
-                               strcpy(up->call, sp->call);
-                               memcpy((char *)&pp->refid, up->call, 4);
-                               memcpy((char *)&peer->refid, up->call,
-                                   4);
-                               up->status |= MSYNC | (sp->ident &
-                                   (SELV | SELH));
-                               if (!(up->status & INSYNC)) {
-                                       up->rsec = (MINUTE -
-                                           sp->mepoch) / SECOND;
-                                       if (!(up->status & SSYNC)) {
-                                               up->repoch =
-                                                   sp->mepoch % SECOND;
-                                               up->yepoch =
-                                                   up->repoch -
-                                                   up->pdelay;
-                                               if (up->yepoch < 0)
-                                                       up->yepoch +=
-                                                           SECOND;
-                                       }
+                               sp = &up->mitig[up->achan].wwvh;
+                       if (sp->count == 0 || up->watch >= ACQSN) {
+                               up->watch = sp->count = 0;
+                               up->schan = (up->schan + 1) % NCHAN;
+                               wwv_qsy(peer, up->schan);
+                       } else if (sp->count >= AMIN) {
+                               up->watch = up->swatch = 0;
+                               up->status |= MSYNC;
+                               ltemp = sp->mepoch - SYNSIZ;
+                               if (ltemp < 0)
+                                       ltemp += MINUTE;
+                               up->rsec = (MINUTE - ltemp) / SECOND;
+                               if (!(up->status & SSYNC)) {
+                                       up->repoch = ltemp % SECOND;
+                                       up->yepoch = up->repoch -
+                                           up->pdelay;
+                                       if (up->yepoch < 0)
+                                               up->yepoch += SECOND;
                                }
+                               wwv_newchan(peer);
                        }
-                       if (up->count == 0) {
-                               up->status &= ~MSYNC;
+               } else {
+
+                       /*
+                        * If minute sync has been acquired, the program
+                        * watches for timeout. The timeout is reset
+                        * when the clock is set or verified. If a
+                        * timeout occurs and the minute units digit has
+                        * not synchronized, reset the program and start
+                        * over.
+                        */
+                       if (up->watch > DIGIT && !(up->status & DSYNC))
+                               up->watch = up->status = 0;
+
+                       /*
+                        * If the second sync amplitude sinks beneath
+                        * the waves or if it times out, dim the sync
+                        * lamp and raises an alarm.
+                        */
+                       up->swatch++;
+                       if (!(up->status & SSYNC))
                                up->alarm |= 1 << SYNERR;
+                       else if ((up->epomax < STHR && !(up->status &
+                           SFLAG)) || up->swatch > HSPEC) {
+                               up->status &= ~SSYNC;
+                               up->swatch = 0;
                        }
                }
        }
-       up->mphase = (up->mphase + 1) % MINUTE;
 
        /*
         * The second sync pulse is extracted using 5-ms FIR matched
@@ -1242,6 +1423,7 @@ wwv_rf(
         * a resolution of one sample (125 us).
         */
        if (up->status & SELV) {
+               up->pdelay = up->cdelay;
 
                /*
                 * WWV FIR matched filter, five cycles of 1000-Hz
@@ -1289,6 +1471,7 @@ wwv_rf(
                mfsync += (mf[1] = mf[0]) * -4.224514e-02;
                mf[0] = syncx;
        } else if (up->status & SELH) {
+               up->pdelay = up->hdelay;
 
                /*
                 * WWVH FIR matched filter, six cycles of 1200-Hz
@@ -1340,38 +1523,45 @@ wwv_rf(
        /*
         * Extract the seconds sync pulse using a 1-s comb filter at
         * baseband. Correct for the FIR matched filter delay, which is
-        * 5 ms for both the WWV and WWVH filters.
+        * 5 ms for both the WWV and WWVH filters. Blank the signal when
+        * probing.
         */
-       i = up->epoch;
-       if (i == 0) {
+       up->epoch = (up->epoch + 1) % SECOND;
+       if (up->epoch == 0) {
                wwv_endpoc(peer, epomax, epopos);
                up->epomax = epomax;
                epomax = 0;
        }
-       dtemp = (epobuf[i] += (mfsync - epobuf[i]) / (MINAVG <<
-           up->avgint));
-       if (dtemp > epomax) {
+       dtemp = (epobuf[up->epoch] += (mfsync - epobuf[up->epoch]) /
+           (MINAVG << up->avgint));
+       if (dtemp > epomax && !(up->status & SFLAG)) {
                epomax = dtemp;
-               epopos = i - 5 * MS;
+               epopos = up->epoch - 5 * MS;
                if (epopos < 0)
                        epopos += SECOND;
        }
-       up->epoch = (i + 1) % SECOND;
 }
 
 
 /*
- * wwv_qrz() - identify and acquire WWV/WWVH minute sync pulse
+ * wwv_qrz - identify and acquire WWV/WWVH minute sync pulse
+ *
+ * This routine implements a virtual station process used to acquire
+ * minute sync and to mitigate among the ten frequency and station
+ * combinations. During minute sync acquisition, the process probes each
+ * frequency in turn for the minute pulse from either station, which
+ * involves searching through the entire epoch minute of samples. After
+ * minute sync acquisition, the process searches only during the probe
+ * window, which occupies seconds 59, 0 and 1, to construct a metric
+ * used to determine which frequency and station provides the best
+ * signal.
  *
- * This routine searches through the entire epoch minute in samples
- * using a filter matched to the 800-ms pulse sent at the beginning of
- * the minute. It then saves the maximum and epoch index of the pulse
- * and compares with subsequent samples. A pulse discriminator is used
- * to groom the samples. For acquisition, it requires that (a) the peak
- * on-pulse sample amplitude must be above a threshold, (b) the SNR
- * relative to the peak off-pulse sample amplitude must be 6 dB or more
- * below the peak and (c) the maximum difference between the current and
- * previous epoch indices must be less than 20 ms.
+ * The pulse discriminator requires that (a) the peak on-pulse sample
+ * amplitude must be above 2000, (b) the SNR relative to the peak
+ * off-pulse sample amplitude must be reduced 6 dB or more below the
+ * peak and (c) the maximum difference between the current and previous
+ * epoch indices must be less than 50 ms. A compare counter keeps track
+ * of the number of successive intervals which satisfy these criteria.
  *
  * Students of radar receiver technology will discover this algorithm
  * amounts to a range gate discriminator. In practice, the performance
@@ -1384,145 +1574,143 @@ wwv_qrz(
        struct peer *peer,      /* peerstructure pointer */
        struct sync *sp,        /* sync channel structure */
        double syncx            /* bandpass filtered sync signal */
-)
+       )
 {
        struct refclockproc *pp;
        struct wwvunit *up;
        char tbuf[80];          /* monitor buffer */
        double snr;             /* on-pulse/off-pulse ratio (dB) */
-       double dtemp;
-       long jitter, epoch;
-       int i;
+       long epoch;
+       int isgood;
 
        pp = peer->procptr;
        up = (struct wwvunit *)pp->unitptr;
 
        /*
-        * Grind the sync pulses through a 800-ms synchronous matched
-        * filter tuned to the sync frequency, 1000 Hz for WWV or 1200
-        * Hz for WWVH. As the local oscillator is not good enough to
-        * synchronously correlate from minute to minute, use only the
-        * phasor amplitude and ignore the phase.
-        */
-       i = sp->sinptr;
-       sp->sinptr = (sp->sinptr + sp->incr) % 80;
-       dtemp = sintab[i] * syncx / SYNSIZ * SGAIN;
-       sp->iamp = sp->iamp - sp->ibuf[up->jptr] + dtemp;
-       sp->ibuf[up->jptr] = dtemp;
-       i = (i + 20) % 80;
-       dtemp = sintab[i] * syncx / SYNSIZ * SGAIN;
-       sp->qamp = sp->qamp - sp->qbuf[up->jptr] + dtemp;
-       sp->qbuf[up->jptr] = dtemp;
-       dtemp = sp->iamp * sp->iamp + sp->qamp * sp->qamp;
-
-       /*
-        * Find the sample with peak energy within the window, which
-        * defines the minute epoch. If the clock has been set, center
-        * the window on the current minute epoch and search only the
-        * window for the peak. Otherwise, center the window on the last
-        * minute epoch found. In the latter case, if the station has
-        * been found, search only the window for the peak; otherwise,
-        * search the entire minute. In all cases find the peak energy
-        * outside the window for use as a noise metric.
+        * Find the sample with peak energy, which defines the minute
+        * epoch. If minute sync has been acquired, search only the
+        * probe window; otherwise, search the entire minute. If a
+        * maximum has been found with good amplitude, search only the
+        * second before and after that position for the next maximum
+        * and the rest of the window for the noise.
         */
-       if (up->status & INSYNC)
-               epoch = (up->nepoch + SYNSIZ) % MINUTE;
-       else
-               epoch = sp->lastpos;
-       if (abs(MOD(up->mphase - epoch, MINUTE)) < SYNSIZ) {
-               if (dtemp > sp->max) {
-                       sp->max = dtemp;
+       if (!(up->status & MSYNC) || up->status & SFLAG) {
+               sp->amp = syncx;
+               if (up->status & MSYNC)
+                       epoch = up->nepoch;
+               else if (sp->count > 1)
+                       epoch = sp->mepoch;
+               else
+                       epoch = sp->lastpos;
+               if (syncx > sp->max) {
+                       sp->max = syncx;
                        sp->pos = up->mphase;
                }
-       } else {
-               if (!(up->status & INSYNC) && !(sp->ident &
-                   up->status) && dtemp > sp->max) {
-                       sp->max = dtemp;
-                       sp->pos = up->mphase;
+               if (abs(MOD(up->mphase - epoch, MINUTE)) > SYNSIZ &&
+                   syncx > sp->noise) {
+                       sp->noise = syncx;
                }
-               if (dtemp > sp->noise)
-                       sp->noise = dtemp;
        }
        if (up->mphase == 0) {
 
                /*
-                * At the end of the minute epoch, determine the epoch
-                * at the beginning of the sync pulse, as well as the
-                * difference between the current and previous epoch
-                * (jitter).
+                * At the end of the minute, determine the epoch of the
+                * sync pulse, as well as the SNR and difference between
+                * the current and previous epoch (jitter).
                 */
-               jitter = MOD(sp->pos - sp->lastpos, MINUTE);
+               sp->jitter = MOD(sp->pos - sp->lastpos, MINUTE);
+               sp->select &= ~JITRNG;
+               if (abs(sp->jitter) > AWND * MS)
+                       sp->select |= JITRNG;
                sp->max = sqrt(sp->max);
                sp->noise = sqrt(sp->noise);
-               snr = wwv_snr(sp->max, sp->noise);
-               switch (sp->count) {
-
-               /*
-                * In state 0 the station has not been heard for some
-                * time and the station identifier bit is cleared. Look
-                * for the biggest blip greater than the amplitude
-                * threshold in the minute and assume that the minute
-                * sync pulse. If found, bump to state 1.
-                */
-               case 0:
-                       if (sp->max >= ATHR)
-                               sp->count++;
-                       break;
-
-               /*
-                * In state 1 a candidate blip has been found and the
-                * next minute is being searched for other blips. If
-                * none is found greater than the threshold, or if the
-                * biggest blip outside the candidate pulse is less than
-                * 6 dB below the biggest blip, drop back to state 0 and
-                * hunt some more. Otherwise, a legitimate minute pulse
-                * has been found, so bump to state 2.
-                */
-               case 1:
-                       if (sp->max < ATHR) {
-                               sp->count--;
-                               up->status &= ~sp->ident;
-                               break;
-                       } else if (snr < ASNR || abs(jitter) > AWND *
-                           MS) {
-                               break;
+               if (up->status & MSYNC) {
+
+                       /*
+                        * If in minute sync, just count the runs up and
+                        * down.
+                        */
+                       if (sp->select & (DATANG | SYNCNG | JITRNG)) {
+                               if (sp->count > 0)
+                                       sp->count--;
+                       } else {
+                               if (sp->count < AMAX)
+                                       sp->count++;
                        }
-                       up->status |= sp->ident & (WWV | WWVH);
-                       /* fall through */
+               } else {
 
-               /*
-                * In states 2 and above the blip is confirmed
-                * legitimate and the station identifier bit is set.
-                * Continue to groom samples as before and drop back to
-                * the previous state if the groom fails. If it
-                * succeeds, bump to the next state. If the bump reaches
-                * the threshold, the minute epoch has been reliably
-                * determined.
-                */
-               default:
-                       if (sp->max < ATHR || snr < ASNR || abs(jitter)
-                           > AWND * MS) {
-                               sp->count--;
+                       /*
+                        * If not yet in minute sync, we have to do a
+                        * little dance to find a valid minute sync
+                        * pulse, emphasis valid.
+                        */
+                       snr = wwv_snr(sp->max, sp->noise);
+                       isgood = sp->max > ATHR && snr > ASNR &&
+                           !(sp->select & JITRNG);
+                       switch (sp->count) {
+
+                       /*
+                        * In state 0 the station was not heard during
+                        * the previous probe. Look for the biggest blip
+                        * greater than the amplitude threshold in the
+                        * minute and assume that the minute sync pulse.
+                        * If found, bump to state 1.
+                        */
+                       case 0:
+                               if (sp->max >= ATHR)
+                                       sp->count++;
                                break;
+
+                       /*
+                        * In state 1 a candidate blip has been found
+                        * and the next minute has been searched for
+                        * another blip. If none are found greater than
+                        * the threshold, or if the biggest blip outside
+                        * the candidate pulse is less than 6 dB below
+                        * the biggest blip, drop back to state 0 and
+                        * hunt some more. Otherwise, a legitimate
+                        * minute pulse may have been found, so bump to
+                        * state 2.
+                        */
+                       case 1:
+                       if (sp->max < ATHR) {
+                                       sp->count--;
+                                       break;
+                               } else if (!isgood) {
+                                       break;
+                               }
+                               /* fall through */
+
+                       /*
+                        * In states 2 and above, continue to groom
+                        * samples as before and drop back to the
+                        * previous state if the groom fails. If it
+                        * succeeds, bump to the next state until
+                        * reaching the clamp, if ever.
+                        */
+                       default:
+                               if (!isgood) {
+                                       sp->count--;
+                                       break;
+                               }
+                               sp->mepoch = sp->pos;
+                               if (sp->count < AMAX)
+                                       sp->count++;
+                                       break;
                        }
-                       sp->mepoch = sp->pos - SYNSIZ;
-                       if (sp->mepoch < 0)
-                               sp->mepoch += MINUTE;
-                       if (sp->count < ACMP)
-                               sp->count++;
-                       break;
-               }
-               sprintf(tbuf,
-                   "wwv8 %2d %04x %5.0f %s %d %5.0f %5.1f %7ld %7ld %7ld",
-                   up->rsec, up->status, up->epomax, sp->call,
-                   sp->count, sp->max, snr, sp->pos, jitter,
-                   MOD(sp->pos - up->nepoch - SYNSIZ, MINUTE));
-               if (pp->sloppyclockflag & CLK_FLAG4)
-                       record_clock_stats(&peer->srcadr, tbuf);
+                       sprintf(tbuf,
+           "wwv8 %2d %04x %5.0f %s %d %5.0f %5.1f %7ld %7ld %7ld",
+                           up->rsec, up->status, up->epomax, sp->ident,
+                           sp->count, sp->max, snr, sp->pos,
+                           sp->jitter, MOD(sp->pos - up->nepoch -
+                           SYNSIZ, MINUTE));
+                       if (pp->sloppyclockflag & CLK_FLAG4)
+                               record_clock_stats(&peer->srcadr, tbuf);
 #ifdef DEBUG
-               if (debug)
-                       printf("%s\n", tbuf);
+                       if (debug)
+                               printf("%s\n", tbuf);
 #endif
+               }
                sp->lastmax = sp->max;
                sp->lastpos = sp->pos;
                sp->max = sp->noise = 0;
@@ -1531,7 +1719,7 @@ wwv_qrz(
 
 
 /*
- * wwv_endpoc() - process receiver epoch
+ * wwv_endpoc - process receiver epoch
  *
  * This routine is called at the end of the receiver epoch. It
  * determines the epoch position within the second and disciplines the
@@ -1608,7 +1796,6 @@ wwv_endpoc(
        }
 
        /*
-        * The candidate epoch is discarded and the SYNERR alarm raised          * if the pulse amplitude is less than the decision threshold.
         * If the epoch candidate is within 1 ms of the last one, the
         * new candidate replaces the last one and the jitter counter is
         * reset; otherwise, the candidate is ignored and the jitter
@@ -1620,9 +1807,7 @@ wwv_endpoc(
         * epoch is reset and the receiver second epoch is set.
         */
        tmp2 = MOD(tepoch - xepoch, SECOND);
-       if (epomax < STHR) {
-               up->status &= ~SSYNC;
-               up->alarm |= 1 << SYNERR;
+       if (up->epomax < STHR) {
                jitcnt = syncnt = avgcnt = 0;
        } else if (abs(tmp2) <= MS || jitcnt >= (MINAVG << up->avgint))
            {
@@ -1635,6 +1820,7 @@ wwv_endpoc(
                                syncnt++;
                        } else {
                                up->status |= SSYNC;
+                               up->swatch = 0;
                                up->repoch = tepoch;
                                up->yepoch = up->repoch - up->pdelay;
                                if (up->yepoch < 0)
@@ -1739,6 +1925,9 @@ wwv_rsec(
        static double bitvec[61]; /* bit integrator for misc bits */
        struct refclockproc *pp;
        struct wwvunit *up;
+       struct chan *cp;
+       struct sync *sp, *rp;
+       char tbuf[80];          /* monitor buffer */
        double rval;            /* likelihood value */
        int sw, arg, nsec;
 
@@ -1768,7 +1957,70 @@ wwv_rsec(
        /*
         * Ignore this second.
         */
-       case IDLE:
+       case IDLE:                      /* 9, 45-49 */
+               break;
+
+       /*
+        * Compute the max and min of both the sync and data pulses for
+        * use later in channel mitigation. The WWV/H format contains
+        * data pulses in second 59 (position identifier) and second 1
+        * (not used), and a sync pulse in second 0. At the end of
+        * second 58, QSY to the probe channel, which rotates over all
+        * WWV/H frequencies. At the end of second 59, latch the sync
+        * noise and data peak. At the end of second 0, latch the sync
+        * peak and data noise. At the end of second 1, latch and
+        * average the sync noise and data peak. Finally, QSY back to
+        * the data channel.
+        */
+       case SYNC2:                     /* 0 */
+               cp = &up->mitig[up->achan];
+               cp->datmin = up->datmax;
+               sp = &cp->wwv;
+               sp->synmax = sp->synamp;
+               sp = &cp->wwvh;
+               sp->synmax = sp->synamp;
+               break;
+
+       case SYNC3:                     /* 1 */
+               cp = &up->mitig[up->achan];
+               cp->datmax = (cp->datmax + up->datmax) / 2;
+               cp->datsnr = wwv_snr(cp->datmax, cp->datmin);
+
+               sp = &cp->wwv;
+               sp->synmin = (sp->synmin + sp->synamp) / 2;
+               sp->synsnr = wwv_snr(sp->synmax, sp->synmin);
+               sp->select &= ~(DATANG | SYNCNG);
+               if (cp->datmax < QTHR || cp->datsnr < QSNR)
+                       sp->select |= DATANG;
+               if (sp->synmax < QTHR || sp->synsnr < QSNR)
+                       sp->select |= SYNCNG;
+
+               rp = &cp->wwvh;
+               rp->synmin = (rp->synmin + rp->synamp) / 2;
+               rp->synsnr = wwv_snr(rp->synmax, rp->synmin);
+               rp->select &= ~(DATANG | SYNCNG);
+               if (cp->datmax < QTHR || cp->datsnr < QSNR)
+                       rp->select |= DATANG;
+               if (rp->synmax < QTHR || rp->synsnr < QSNR)
+                       rp->select |= SYNCNG;
+
+               if (up->status & MSYNC) {
+                       sprintf(tbuf,
+    "agc %d data %.0f/%.1f sync %3s %04x %d %.0f/%.1f/%ld %3s %04x %d %.0f/%.1f/%ld",
+                           up->gain, cp->datmax, cp->datsnr, sp->ident,
+                           sp->select, sp->count, sp->synmax,
+                           sp->synsnr, sp->jitter, rp->ident,
+                           rp->select, rp->count, rp->synmax,
+                           rp->synsnr, rp->jitter);
+                       if (pp->sloppyclockflag & CLK_FLAG4)
+                               record_clock_stats(&peer->srcadr, tbuf);
+#ifdef DEBUG
+                       if (debug)
+                               printf("wwv5 %s\n", tbuf);
+#endif /* DEBUG */
+                       up->status &= ~SFLAG;
+                       wwv_newchan(peer);
+               }
                break;
 
        /*
@@ -1778,17 +2030,18 @@ wwv_rsec(
         * considered valid. Bits not used in the digit are forced to
         * zero and not checked for errors.
         */
-       case COEF1:
+       case COEF1:                     /* 10-13 */
                if (up->status & DGATE)
                        up->status |= BGATE;
                bcddld[arg] = bit;
                break;
 
-       case COEF2:
+       case COEF2:                     /* 18, 27-28, 42-43 */
                bcddld[arg] = 0;
                break;
 
-       case COEF:
+       case COEF:                      /* 4-7, 15-17, 20-23, 25-26,
+                                          30-33, 35-38, 40-41, 51-54 */ 
                if (up->status & DGATE || !(up->status & DSYNC))
                        up->status |= BGATE;
                bcddld[arg] = bit;
@@ -1800,38 +2053,53 @@ wwv_rsec(
         * digits correlating each with the coefficients and saving the
         * greatest and the next lower for later SNR calculation.
         */
-       case DECIM2:
+       case DECIM2:                    /* 29 */
                wwv_corr4(peer, &up->decvec[arg], bcddld, bcd2);
                break;
 
-       case DECIM3:
+       case DECIM3:                    /* 44 */
                wwv_corr4(peer, &up->decvec[arg], bcddld, bcd3);
                break;
 
-       case DECIM6:
+       case DECIM6:                    /* 19 */
                wwv_corr4(peer, &up->decvec[arg], bcddld, bcd6);
                break;
 
-       case DECIM9:
+       case DECIM9:                    /* 8, 14, 24, 34, 39 */
                wwv_corr4(peer, &up->decvec[arg], bcddld, bcd9);
                break;
 
        /*
         * Miscellaneous bits. If above the positive threshold, declare
         * 1; if below the negative threshold, declare 0; otherwise
-        * raise the SYMERR alarm.
+        * raise the SYMERR alarm. At the end of minute 58, QSY to the
+        * probe channel.
         */
-       case MSC20:
+       case MSC20:                     /* 55 */
                wwv_corr4(peer, &up->decvec[YR + 1], bcddld, bcd9);
                /* fall through */
 
-       case MSCBIT:
+       case MSCBIT:                    /* 2, 3, 50, 56-57 */
+               if (bitvec[up->rsec] > BTHR)
+                       up->misc |= arg;
+               else if (bitvec[up->rsec] < -BTHR)
+                       up->misc &= ~arg;
+               else
+                       up->alarm |= 1 << SYMERR;
+               break;
+
+       case MSC21:                     /* 58 */
                if (bitvec[up->rsec] > BTHR)
                        up->misc |= arg;
                else if (bitvec[up->rsec] < -BTHR)
                        up->misc &= ~arg;
                else
                        up->alarm |= 1 << SYMERR;
+               if (up->status & MSYNC) {
+                       up->schan = (up->schan + 1) % NCHAN;
+                       wwv_qsy(peer, up->schan);
+                       up->status |= SFLAG;
+               }
                break;
 
        /*
@@ -1847,37 +2115,53 @@ wwv_rsec(
         * test this wrinkle at intervals of about 18 months, so your
         * mileage may vary.
         */
-       case MIN1:
+       case MIN1:                      /* 59 */
+               cp = &up->mitig[up->achan];
+               cp->datmax = up->datmax;
+               sp = &cp->wwv;
+               sp->synmin = sp->synamp;
+               sp = &cp->wwvh;
+               sp->synmin = sp->synamp;
                if (up->tsec == 60) {
                        up->status &= ~LEPSEC;
                        break;
                }
                /* fall through */
 
-       case MIN2:
+       case MIN2:                      /* 59/60 */
                up->minset = ((current_time - peer->update) + 30) / 60;
                if (up->digcnt > 0)
                        up->status |= DSYNC;
-               if (up->digcnt >= 9 && up->status & SSYNC) {
+               if (up->digcnt >= 9 && (up->alarm & 0x3333) == 0) {
                        up->status |= INSYNC;
-                       pp->lencode = timecode(up, pp->a_lastcode);
-                       record_clock_stats(&peer->srcadr,
-                           pp->a_lastcode);
-                       refclock_receive(peer);
-               } else {
-                       pp->lencode = timecode(up, pp->a_lastcode);
-                       if (pp->sloppyclockflag & CLK_FLAG4)
-                               record_clock_stats(&peer->srcadr,
-                                   pp->a_lastcode);
+                       up->watch = 0;
                }
+               pp->lencode = timecode(up, pp->a_lastcode);
+               if (up->misc & SECWAR)
+                       pp->leap = LEAP_ADDSECOND;
+               else
+                       pp->leap = LEAP_NOWARNING;
+               refclock_receive(peer);
+               record_clock_stats(&peer->srcadr, pp->a_lastcode);
 #ifdef DEBUG
                if (debug)
                        printf("wwv: timecode %d %s\n", pp->lencode,
                            pp->a_lastcode);
 #endif /* DEBUG */
+
+               /*
+                * The ultimate watchdog is the interval since the
+                * reference clock interface code last received an
+                * update from this driver. If the interval is greater
+                * than a couple of days, manual intervention is
+                * probably required, so the program resets and tries to
+                * resynchronized from scratch.
+                */
+               if (up->minset > PANIC)
+                       up->status = 0;
                up->alarm = (up->alarm & ~0x8888) << 1;
                up->errcnt = up->digcnt = nsec = 0;
-               up->nepoch = up->mphase;
+               up->nepoch = (up->mphase + SYNSIZ) % MINUTE;
                break;
        }
        up->rsec = up->tsec = nsec;
@@ -1886,7 +2170,7 @@ wwv_rsec(
 
 
 /*
- * wwv_data() - calculate bit probability
+ * wwv_data - calculate bit probability
  *
  * This routine is called at the end of the receiver second to calculate
  * the probabilities that the previous second contained a zero (P0), one
@@ -1910,10 +2194,13 @@ wwv_data(
 
        /*
         * If the data amplitude or SNR are below threshold or if the
-        * pulse length is less than 170 ms, declare an erasure.
+        * pulse length is less than 170 ms, declare an erasure. Don't
+        * count the errors if not in minute sync or if in probeing
+        * mode.
         */
-       if (!(up->status & MSYNC) || up->datamp < DTHR ||
-           up->datsnr < DSNR || dpulse < DATSIZ) {
+       if (!(up->status & MSYNC) || up->status & SFLAG)
+               return (0);
+       if (up->sigamp < DTHR || up->datsnr < DSNR || dpulse < DATSIZ) {
                up->status |= DGATE;
                up->errcnt++;
                if (up->errcnt > MAXERR)
@@ -1953,7 +2240,7 @@ wwv_data(
 
 
 /*
- * wwv_corr4() - determine maximum likelihood digit
+ * wwv_corr4 - determine maximum likelihood digit
  *
  * This routine correlates the received digit vector with the BCD
  * coefficient vectors corresponding to all valid digits at the given
@@ -2039,7 +2326,7 @@ wwv_corr4(
        }
        if (vp->digit != mldigit)
                up->alarm |= 1 << DECERR;
-       if (up->status & MSYNC) {
+       if (up->status & MSYNC && !(up->status & INSYNC)) {
                sprintf(tbuf,
                    "wwv4 %2d %04x %5.0f %2d %d %d %d %d %5.0f %5.1f",
                    up->rsec, up->status, up->epomax,  vp->radix,
@@ -2057,7 +2344,7 @@ wwv_corr4(
 
 
 /*
- * wwv_tsec() - transmitter second processing
+ * wwv_tsec - transmitter second processing
  *
  * This routine is called at the end of the transmitter second. It
  * implements a state machine that advances the logical clock subject to
@@ -2143,7 +2430,7 @@ wwv_tsec(
 
 
 /*
- * carry() - process digit
+ * carry - process digit
  *
  * This routine rotates a likelihood vector one position and increments
  * the clock digit modulo the radix. It returns the new clock digit -
@@ -2171,7 +2458,7 @@ carry(
 
 
 /*
- * wwv_snr() - compute SNR or likelihood function
+ * wwv_snr - compute SNR or likelihood function
  */
 static double
 wwv_snr(
@@ -2181,19 +2468,136 @@ wwv_snr(
 {
        double rval;
 
-       if (signal <= 0 && noise <= 0)
-               return (0);
-       if (noise <= 0)
-               return (30);
-       rval = 20 * log10(signal / noise);
-       if (rval > 30)
-               return (30);
+       /*
+        * This is a little tricky. Due to the way things are measured,
+        * either or both the signal or noise amplitude could negative
+        * or equal to zero. The intent is that, if the signal is
+        * negative or zero, the SNR must always be zero. This can
+        * happen with the subcarrier SNR before the phase has been
+        * aligned. On the other hand, in the likelihood function the
+        * "noise" is the next maximum down from the peak and this could
+        * be negative. However, in this case the SNR is truly
+        * stupendous, so cap at MAXSNR dB.
+        */
+       if (signal <= 0) {
+               rval = 0;
+       } else if (noise <= 0) {
+               rval = MAXSNR;
+       } else {
+               rval = 20 * log10(signal / noise);
+               if (rval > MAXSNR)
+                       rval = MAXSNR;
+       }
        return (rval);
 }
 
+/*
+ * wwv_newchan - change to new data channel
+ *
+ * Assuming the radio can be tuned by this program, it actually appears
+ * as a 10-channel receiver, one channel for each of WWV and WWVH on
+ * each of five frequencies. While the radio is tuned to the working
+ * data channel (frequency and station) for most of the minute, during
+ * seconds 59, 0 and 1 the radio is tuned to a probe channel, in order
+ * to pick up minute sync pulses. The search for WWV and WWVH stations
+ * operates simultaneously, with WWV on 1000 Hz and WWVH on 1200 Hz. The
+ * probe channel rotates for each minute over the five frequencies
+ * picking up the minute pulse peak and other data. At the end of each
+ * rotation, this routine mitigates over all channels and chooses the
+ * best frequency and station.
+ */
+void
+wwv_newchan(
+       struct peer *peer       /* peer structure pointer */
+       )
+{
+       struct refclockproc *pp;
+       struct wwvunit *up;
+       struct chan *cp;
+       struct sync *sp, *rp;
+       int rank;
+       int i, j;
+
+       pp = peer->procptr;
+       up = (struct wwvunit *)pp->unitptr;
+
+       /*
+        * Reset the filter selector and station pointer to avoid
+        * fooling around should we lose this game.
+        */
+       up->sptr = 0;
+       up->status &= ~(SELV | SELH);
+
+       /*
+        * Search all five station pairs looking for the station with
+        * the best metric, here defined by the compare counter.
+        */
+       j = 0;
+       sp = (struct sync *)0;
+       rank = 0;
+       for (i = 0; i < NCHAN; i++) {
+               cp = &up->mitig[i];
+               rp = &cp->wwvh;
+               if (rp->count >= rank) {
+                       sp = rp;
+                       rank = rp->count;
+                       j = i;
+               }
+               rp = &cp->wwv;
+               if (rp->count >= rank) {
+                       sp = rp;
+                       rank = rp->count;
+                       j = i;
+               }
+       }
+
+       /*
+        * If we find a station, continue to track it. If not, track the
+        * one going now.
+        */
+       if (rank > 0) {
+               up->dchan = j;
+               up->sptr = sp;
+               up->status |= sp->select & (SELV | SELH);
+               memcpy((char *)&pp->refid, sp->refid, 4);
+               memcpy((char *)&peer->refid, sp->refid, 4);
+               wwv_qsy(peer, up->dchan);
+       }
+}
+
+
+/*
+ * wwv_qsy - Tune ICOM receiver
+ *
+ * This routine saves the current AGC for the current channel, switches
+ * to a new channel and restores the AGC for that channel. If a tunable
+ * receiver is not available, just fake it.
+ */
+void
+wwv_qsy(
+       struct peer *peer,      /* peer structure pointer */
+       int     chan            /* channel */
+       )
+{
+       struct refclockproc *pp;
+       struct wwvunit *up;
+
+       pp = peer->procptr;
+       up = (struct wwvunit *)pp->unitptr;
+       up->mitig[up->achan].gain = up->gain;
+#ifdef ICOM
+       if (up->fd_icom >= 0)
+               icom_freq(peer->ttl, qsy[chan]);
+#endif /*  */
+       up->achan = chan;
+       up->gain = up->mitig[up->achan].gain;
+       up->clipcnt = 0;
+       return;
+}
+
 
 /*
- * timecode() - assemble timecode string and length
+ * timecode - assemble timecode string and length
  *
  * Prettytime format - similar to Spectracom
  *
@@ -2212,7 +2616,7 @@ wwv_snr(
  * dut DUT sign and magnitude in deciseconds
  * lset        minutes since last clock update
  * agc audio gain (0-255)
- * stn station identifier ('X', 'C', 'H', 'M')
+ * stn station identifier (station and frequency)
  * comp        minute sync compare counter
  * errs        bit errors in last minute\v * freq       frequency offset (PPM)\v * avgt  averaging time (s)\v */
 int
@@ -2221,9 +2625,11 @@ timecode(
        char *ptr               /* target string */
        )
 {
+       struct sync *sp;
        int year, day, hour, minute, second, frac, dut;
-       char synchar, qual, leapchar, dst, duts, ident;
-       char *cptr;
+       char synchar, qual, leapchar, dst;
+       char cptr[50];
+       
 
        /*
         * Common fixed-format fields
@@ -2251,29 +2657,28 @@ timecode(
        frac = (up->tphase * 1000) / SECOND;
        leapchar = (up->misc & SECWAR) ? 'L' : ' ';
        dst = dstcod[(up->misc >> 4) & 0x3];
-       duts = (up->misc & DUTS) ? '+' : '-';
        dut = up->misc & 0x7;
+       if (!(up->misc & DUTS))
+               dut = -dut;
+       sprintf(ptr, "%c%1X", synchar, qual);
+       sprintf(cptr, " %4d %03d %02d:%02d:%02d.%.03d %c%c %+d",
+           year, day, hour, minute, second, frac, leapchar, dst, dut);
+       strcat(ptr, cptr);
 
        /*
         * Specific variable-format fields
         */
-       if (up->status & SELV && up->status & SELH)
-               ident = 'M';
-       else if (up->status & SELV)
-               ident = 'C';
-       else if (up->status & SELH)
-               ident = 'H';
+       sp = up->sptr;
+       if (sp != 0)
+               sprintf(cptr, " %d %d %s %d %d %.1f %d", up->minset,
+                   up->mitig[up->dchan].gain, sp->ident, sp->count,
+                   up->errcnt, up->freq / SECOND * 1e6, MINAVG <<
+                   up->avgint);
        else
-               ident = 'X';
-       cptr = ptr;
-       sprintf(cptr, "%c%1X", synchar, qual);
-       cptr = ptr + strlen(ptr);
-       sprintf(cptr, " %4d %03d %02d:%02d:%02d.%.03d", year, day,
-           hour, minute, second, frac);
-       cptr = ptr + strlen(ptr);
-       sprintf(cptr, " %c%c %c%d %d %d %c %d %d %.1f %d", leapchar,
-           dst, duts, dut, up->minset, up->gain, ident, up->count,
-           up->errcnt, up->freq / SECOND * 1e6, MINAVG << up->avgint);
+               sprintf(cptr, " %d %d X 0 %d %.1f %d", up->minset,
+                   up->mitig[up->dchan].gain, up->errcnt, up->freq /
+                   SECOND * 1e6, MINAVG << up->avgint);
+       strcat(ptr, cptr);
        return (strlen(ptr));
 }
 
@@ -2314,6 +2719,7 @@ wwv_gain(
                if (up->gain < AUDIO_MIN_GAIN)
                        up->gain = AUDIO_MIN_GAIN;
        }
+       up->clipcnt = 0;
        AUDIO_INITINFO(&info);
        info.record.port = up->port;
        info.record.gain = up->gain;
@@ -2351,6 +2757,7 @@ wwv_audio(
         * Set audio device parameters.
         */
        AUDIO_INITINFO(&info);
+       info.record.gain = (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN) / 2;
        info.record.buffer_size = AUDIO_BUFSIZ;
        if (ioctl(wwv_ctl_fd, (int)AUDIO_SETINFO, &info) < 0) {
                perror("AUDIO_SETINFO");