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
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
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>
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
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
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
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
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
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
<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>
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
</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>
<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>
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>
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
<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
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>
<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
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.
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
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
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.
<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
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>
<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>
#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
*
* 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
#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%) */
#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
#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
#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 */
{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 */
};
* 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 */
};
/*
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 */
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
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 */
* 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 */
};
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
{
struct refclockproc *pp;
struct wwvunit *up;
+ struct chan *cp;
+#ifdef ICOM
+ int temp;
+#endif /* ICOM */
/*
* Local variables
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);
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 */
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);
}
* 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;
}
/*
*
* 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(
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;
/*
- * wwv_epoch() main loop
+ * wwv_epoch - main loop
*
* This routine establishes receiver and transmitter epoch
* synchronization and determines the data subcarrier pulse length.
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.
/*
* 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
/*
* 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;
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,
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;
}
}
*/
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);
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
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 */
static int iniflg; /* initialization flag */
struct sync *sp;
double dtemp;
+ long ltemp;
int i;
pp = peer->procptr;
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 */
* 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;
* 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
* a resolution of one sample (125 us).
*/
if (up->status & SELV) {
+ up->pdelay = up->cdelay;
/*
* WWV FIR matched filter, five cycles of 1000-Hz
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
/*
* 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
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;
/*
- * 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
}
/*
- * 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
* 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))
{
syncnt++;
} else {
up->status |= SSYNC;
+ up->swatch = 0;
up->repoch = tepoch;
up->yepoch = up->repoch - up->pdelay;
if (up->yepoch < 0)
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;
/*
* 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;
/*
* 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;
* 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;
/*
* 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;
/*
- * 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
/*
* 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)
/*
- * 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
}
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,
/*
- * 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
/*
- * 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 -
/*
- * wwv_snr() - compute SNR or likelihood function
+ * wwv_snr - compute SNR or likelihood function
*/
static double
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
*
* 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
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
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));
}
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;
* 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");