* This driver synchronizes the computer time using data encoded in
* radio transmissions from NIST time/frequency stations WWV in Boulder,
* CO, and WWVH in Kauai, HI. Transmissions 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 this
- * program as propagation conditions change throughout the day and
- * night.
+ * 2.5, 5, 10 and 15 MHz from WWV and WWVH, and 20 MHz from WWV. An
+ * ordinary AM 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 this program as propagation conditions
+ * change throughout the weasons, both day and night.
*
* The driver receives, demodulates and decodes the radio signals when
* connected to the audio codec of a workstation running Solaris, SunOS
* TI 320C25 digital signal processor described in: Mills, D.L. A
* precision radio clock for WWV transmissions. Electrical Engineering
* Report 97-8-1, University of Delaware, August 1997, 25 pp., available
- * from www.eecis.udel.edu/~mills/reports.htm. The algorithms described
+ * from www.eecis.udel.edu/~mills/reports.html. The algorithms described
* in this report have been modified somewhat to improve performance
* under weak signal conditions and to provide an automatic station
* identification feature.
* the monitor gain is set to a default value.
*/
/*
- * General definitions. Note the DGAIN parameter might need to be
- * changed to fit the audio response of the radio at 100 Hz. The
- * WWV/WWVH data subcarrier is transmitted at 10 dB down from 100
- * percent modulation; however, the matched filter boosts it by a factor
- * of 17 and the receiver bandpass does what it does. The compromise
- * value here is five. Your milege may vary.. The FREQ_OFFSET parameter
- * can be used as a frequency vernier to correct codec frequency if
- * greater than MAXFREQ.
+ * General definitions. These ordinarily do not need to be changed.
*/
#define DEVICE_AUDIO "/dev/audio" /* audio device name */
#define AUDIO_BUFSIZ 320 /* audio buffer size (50 ms) */
#define TCKCYC 5 /* tick filter cycles */
#define TCKSIZ (TCKCYC * MS) /* tick filter size */
#define NCHAN 5 /* number of radio channels */
-#define DCHAN 3 /* default radio channel (15 Mhz) */
#define AUDIO_PHI 5e-6 /* dispersion growth factor */
+
+/*
+ * Tunable parameters. The DGAIN parameter can be changed to fit the
+ * audio response of the radio at 100 Hz. The WWV/WWVH data subcarrier
+ * is transmitted at about 20 percent percent modulation; the matched
+ * filter boosts it by a factor of 17 and the receiver response does
+ * what it does. The compromise value works for ICOM radios. If the
+ * radio is not tunable, the DCHAN parameter can be changed to fit the
+ * expected best propagation frequency: higher if further from the
+ * transmitter, lower if nearer. The compromise value works for the US
+ * right coast. The FREQ_OFFSET parameter can be used as a frequency
+ * vernier to correct codec requency if greater than MAXFREQ.
+ */
+#define DCHAN 3 /* default radio channel (15 Mhz) */
#define DGAIN 5. /* subcarrier gain */
-#define FREQ_OFFSET 0 /* codec frequency correction (PPM) */
+#define FREQ_OFFSET 0. /* codec frequency correction (PPM) */
/*
* General purpose status bits (status)
*
- * SELV and/or SELH are set when WWV or WWVH has been heard and cleared
+ * SELV and/or SELH are set when WWV or WWVH have been heard and cleared
* on signal loss. SSYNC is set when the second sync pulse has been
* acquired and cleared by signal loss. MSYNC is set when the minute
* sync pulse has been acquired. DSYNC is set when the units digit has
* These bits indicate various alarm conditions, which are decoded to
* form the quality character included in the timecode.
*/
-#define CMPERR 1 /* BCD digit compare error */
+#define CMPERR 1 /* digit or misc bit compare error */
#define LOWERR 2 /* low bit or digit amplitude or SNR */
#define NINERR 4 /* less than nine digits in minute */
#define SYNERR 8 /* not tracking second sync */
* driver starts from scratch. Suitably more refined procedures may be
* developed in future. All these are in minutes.
*/
-#define ACQSN 5 /* station acquisition timeout */
-#define DATA 10 /* unit minutes timeout */
-#define SYNCH 30 /* station sync timeout */
+#define ACQSN 6 /* station acquisition timeout */
+#define DATA 15 /* unit minutes timeout */
+#define SYNCH 40 /* station sync timeout */
#define PANIC (2 * 1440) /* panic timeout */
/*
* rates of the driver. The values defined here may be on the
* adventurous side in the interest of the highest sensitivity.
*/
-#define MTHR 13. /* acquisition signal gate (percent) */
-#define TTHR 50. /* tracking signal gate (percent) */
-#define AWND 20 /* acquisition jitter threshold (ms) */
+#define MTHR 13. /* minute sync gate (percent) */
+#define TTHR 50. /* minute sync threshold (percent) */
+#define AWND 20 /* minute sync jitter threshold (ms) */
#define ATHR 2500. /* QRZ minute sync threshold */
#define ASNR 20. /* QRZ minute sync SNR threshold (dB) */
#define QTHR 2500. /* QSY minute sync threshold */
long mepoch; /* minute synch epoch */
double amp; /* sync signal */
- double syneng; /* sync signal max 800 ms */
- double synmax; /* sync signal max 0 s */
+ double syneng; /* sync signal max */
+ double synmax; /* sync signal max latched at 0 s */
double synsnr; /* sync signal SNR */
double metric; /* signal quality metric */
int reach; /* reachability register */
static double wwv_snr P((double, double));
static int carry P((struct decvec *));
static int wwv_newchan P((struct peer *));
-static void wwv_newgame P((struct peer *, int));
+static void wwv_newgame P((struct peer *));
static double wwv_metric P((struct sync *));
static void wwv_clock P((struct peer *));
#ifdef ICOM
#ifdef DEBUG
if (debug)
audio_show();
-#endif
+#endif /* DEBUG */
/*
* Allocate and initialize unit structure
#ifdef DEBUG
if (debug > 1)
temp = P_TRACE;
-#endif
+#endif /* DEBUG */
if (peer->ttl != 0) {
if (peer->ttl & 0x80)
up->fd_icom = icom_init("/dev/icom", B1200,
/*
* Let the games begin.
*/
- wwv_newgame(peer, DCHAN);
+ wwv_newgame(peer);
return (1);
}
sample = up->comp[~*dpt++ & 0xff];
/*
- * Clip noise spikes greater than MAXAMP. If no clips,
- * increase the gain a tad; if the clips are too high,
- * decrease a tad.
+ * Clip noise spikes greater than MAXAMP (6000) and
+ * record the number of clips to be used later by the
+ * AGC.
*/
if (sample > MAXAMP) {
sample = MAXAMP;
* This routine keeps track of status. If no offset samples have been
* processed during a poll interval, a timeout event is declared. If
* errors have have occurred during the interval, they are reported as
- * well. Once the clock is set, it always appears reachable, unless
- * reset by watchcat timeout.
+ * well.
*/
static void
wwv_poll(
struct refclockproc *pp;
struct wwvunit *up;
struct sync *sp, *rp;
+ int j, k;
static double lpf[5]; /* 150-Hz lpf delay line */
double data; /* lpf output */
static double hsqamp; /* wwvh Q tick amplitude */
static double epobuf[SECOND]; /* epoch sync comb filter */
- static double epomax; /* epoch sync amplitude buffer */
+ static double epomax, nxtmax; /* epoch sync amplitude buffer */
static int epopos; /* epoch sync position buffer */
static int iniflg; /* initialization flag */
- int epoch; /* comb filter index */
int pdelay; /* propagation delay (samples) */
+ int epoch; /* comb filter index */
double dtemp;
int i;
/*
* If minute sync has not been acquired before
- * timeout, or if no signal is heard, the
- * program cycles to the next frequency and
- * tries again.
+ * ACQSN timeout (6 min), or if no signal is
+ * heard, the program cycles to the next
+ * frequency and tries again.
*/
- if (!wwv_newchan(peer) || up->watch > ACQSN) {
+ if (!wwv_newchan(peer))
+ up->watch = 0;
#ifdef ICOM
- if (up->fd_icom > 0)
- up->dchan = (up->dchan + 1) %
- NCHAN;
+ if (up->fd_icom > 0)
+ wwv_qsy(peer, up->dchan);
#endif /* ICOM */
- wwv_newgame(peer, up->dchan);
- }
} else {
/*
if (up->status & MSYNC) {
wwv_epoch(peer);
} else if (up->sptr != NULL) {
- struct chan *cp;
-
sp = up->sptr;
- if (sp->metric >= TTHR && epoch == sp->mepoch % SECOND)
- {
+ if (sp->metric >= TTHR && epoch == sp->mepoch % SECOND) {
up->rsec = (60 - sp->mepoch / SECOND) % 60;
up->rphase = 0;
up->status |= MSYNC;
up->repoch = up->yepoch = epoch;
else
up->repoch = up->yepoch;
-
- /*
- * Clear the crud and initialize fairly.
- */
- for (i = 0; i < NCHAN; i++) {
- cp = &up->mitig[i];
- cp->wwv.count = cp->wwv.reach = 0;
- cp->wwvh.count = cp->wwvh.reach = 0;
- }
- sp->count = sp->reach = 1;
+
}
}
dtemp = (epobuf[epoch] += (mfsync - epobuf[epoch]) /
up->avgint);
if (dtemp > epomax) {
+ j = epoch + 6 * MS;
+ if (j >= SECOND)
+ j -= SECOND;
+ k = epoch - 6 * MS;
+ if (k < 0)
+ k += SECOND;
+ nxtmax = max(fabs(epobuf[j]), fabs(epobuf[k]));
epomax = dtemp;
epopos = epoch;
}
if (epoch == 0) {
- int j;
-
up->epomax = epomax;
- dtemp = 0;
j = epopos - 10 * MS;
if (j < 0)
j += SECOND;
- up->eposnr = wwv_snr(epomax, epobuf[j]);
+ up->eposnr = wwv_snr(epomax, nxtmax);
epopos -= pdelay + TCKCYC * MS;
if (epopos < 0)
epopos += SECOND;
* 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 minute of samples. After
- * finding a candidate, the process searches only the seconds before and
- * after the candidate for the signal and all other seconds for the
- * noise.
+ * frequency and station in turn for the minute pulse, which
+ * involves searching through the entire 480,000-sample minute. The
+ * process finds the maximum signal and RMS noise plus signal. Then, the
+ * actual noise is determined by subtracting the energy of the matched
+ * filter.
*
* Students of radar receiver technology will discover this algorithm
- * amounts to a range gate discriminator. The discriminator requires
- * that the peak minute pulse amplitude be at least 2000 and the SNR be
- * at least 6 dB. In addition after finding a candidate, The peak second
- * pulse amplitude must be at least 2000, the SNR at least 6 dB and the
+ * amounts to a range gate discriminator. A valid pulse must have peak
+ * amplitude at least QTHR (2500) and SNR at least QSNR (20) dB and the
* difference between the current and previous epoch must be less than
- * 7.5 ms, which corresponds to a frequency error of 125 PPM. A compare
- * counter keeps track of the number of successive intervals which
- * satisfy these criteria.
- *
- * Note that, while the minute pulse is found by by the discriminator,
- * the actual value is determined from the second epoch. The assumption
- * is that the discriminator peak occurs about 800 ms into the second,
- * so the timing is retarted to the previous second epoch.
+ * AWND (20 ms). Note that the discriminator peak occurs about 800 ms
+ * into the second, so the timing is retarded to the previous second
+ * epoch.
*/
static void
wwv_qrz(
{
struct refclockproc *pp;
struct wwvunit *up;
- char tbuf[80]; /* monitor buffer */
- long epoch, fpoch;
- int isgood;
+ char tbuf[80]; /* monitor buffer */
+ long epoch;
pp = peer->procptr;
up = (struct wwvunit *)pp->unitptr;
/*
- * Find the sample with peak energy, which defines the minute
- * epoch. If a sample has been found with good energy,
- * accumulate the noise energy for all except the second before
- * and after that position.
- *
- * If the seconds sync lamp is lit, use that as the sample epoch
- * within the second; otherwise, use the minutes sync peak.
- * Little bit of class here.
- */
- if (up->epomax > STHR && up->eposnr > SSNR) {
- fpoch = up->mphase % SECOND - up->yepoch;
- if (fpoch < 0)
- fpoch += SECOND;
- } else {
- fpoch = pdelay + SYNSIZ;
- }
- epoch = up->mphase - fpoch;
+ * Find the sample with peak amplitude, which defines the minute
+ * epoch. Accumulate all samples to determine the total noise
+ * energy.
+ */
+ epoch = up->mphase - pdelay - SYNSIZ;
if (epoch < 0)
epoch += MINUTE;
if (sp->amp > sp->maxeng) {
sp->maxeng = sp->amp;
sp->pos = epoch;
}
- if (abs((epoch - sp->lastpos) % MINUTE) > SECOND)
- sp->noieng += sp->amp;
+ sp->noieng += sp->amp;
/*
- * 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, which represents the
- * intrinsic frequency error plus jitter.
+ * At the end of the minute, determine the epoch of the minute
+ * sync pulse, as well as the difference between the current and
+ * previous epoches due to the intrinsic frequency error plus
+ * jitter. When calculating the SNR, subtract the pulse energy
+ * from the total noise energy and then normalize.
*/
if (up->mphase == 0) {
sp->synmax = sp->maxeng;
- sp->synsnr = wwv_snr(sp->synmax, sp->noieng / (MINUTE -
- 2. * SECOND));
+ sp->synsnr = wwv_snr(sp->synmax, (sp->noieng -
+ sp->synmax) / MINUTE);
+ if (sp->count == 0)
+ sp->lastpos = sp->pos;
epoch = (sp->pos - sp->lastpos) % MINUTE;
-
- /*
- * If not yet in minute sync, we have to do a little
- * dance to find a valid minute sync pulse, emphasis
- * valid.
- */
- isgood = sp->synmax > ATHR && sp->synsnr > ASNR;
- 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. We're fishing here, since
- * the range gate has not yet been determined. If found,
- * bump to state 1.
- */
- case 0:
- if (sp->synmax >= ATHR)
+ sp->reach <<= 1;
+ if (sp->reach & (1 << AMAX))
+ sp->count--;
+ if (sp->synmax > ATHR && sp->synsnr > ASNR) {
+ if (abs(epoch) < AWND * MS) {
+ sp->reach |= 1;
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 acceptable, 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 (!isgood) {
- sp->count = 0;
- break;
- }
- sp->count++;
- break;
-
- /*
- * In states 2 and above, continue to groom samples as
- * before and drop back to state 0 if the groom fails.
- * If it succeeds, set the epoch and bump to the next
- * state until reaching the threshold, if ever.
- */
- default:
- if (!isgood || abs(epoch) > AWND * MS) {
- sp->count = 0;
- break;
+ sp->mepoch = sp->lastpos = sp->pos;
+ } else if (sp->count == 1) {
+ sp->lastpos = sp->pos;
}
- sp->mepoch = sp->pos;
- sp->count++;
- break;
}
- sp->metric = wwv_metric(sp);
+ if (up->watch > ACQSN)
+ sp->metric = 0;
+ else
+ sp->metric = wwv_metric(sp);
if (pp->sloppyclockflag & CLK_FLAG4) {
sprintf(tbuf,
- "wwv8 %d %3d %s %d %5.0f %5.1f %5.0f %5ld %5d %ld",
- up->port, up->gain, sp->refid, sp->count,
- sp->synmax, sp->synsnr, sp->metric, sp->pos,
- up->yepoch, epoch);
+ "wwv8 %04x %3d %s %04x %.0f %.0f/%.1f %4ld %4ld",
+ up->status, up->gain, sp->refid,
+ sp->reach & 0xffff, sp->metric, sp->synmax,
+ sp->synsnr, sp->pos % SECOND, epoch);
record_clock_stats(&peer->srcadr, tbuf);
#ifdef DEBUG
if (debug)
printf("%s\n", tbuf);
-#endif
+#endif /* DEBUG */
}
- sp->lastpos = sp->pos;
sp->maxeng = sp->noieng = 0;
}
}
/*
* If the signal amplitude or SNR fall below thresholds, dim the
* second sync lamp and wait for hotter ions. If no stations are
- * heard we are either in a probe cycle or the ions are really
- * dim.
+ * heard, we are either in a probe cycle or the ions are really
+ * cold.
*/
scount++;
if (up->epomax < STHR || up->eposnr < SSNR) {
tmp2 = (tepoch - xepoch) % SECOND;
if (tmp2 == 0) {
syncnt++;
- if (syncnt > SCMP) {
+ if (syncnt > SCMP && up->status & MSYNC && up->status &
+ FGATE) {
up->status |= SSYNC;
up->yepoch = tepoch;
}
} else if (syncnt >= maxrun) {
maxrun = syncnt;
- mepoch = xepoch;
mcount = scount;
+ mepoch = xepoch;
syncnt = 0;
}
if ((pp->sloppyclockflag & CLK_FLAG4) && !(up->status & MSYNC))
{
sprintf(tbuf,
- "wwv1 %04x %5.0f %5.1f %5d %5d %4d %4d %4d %5d",
- up->status, up->epomax, up->eposnr, tepoch,
- tmp2, avgcnt, syncnt, maxrun, mepoch);
+ "wwv1 %04x %3d %4d %5.0f %5.1f %5d %4d %4d %4d",
+ up->status, up->gain, tepoch, up->epomax,
+ up->eposnr, tmp2, avgcnt, syncnt,
+ maxrun);
record_clock_stats(&peer->srcadr, tbuf);
#ifdef DEBUG
if (debug)
}
/*
- * The sample clock frequency is disciplined using a first order
+ * The sample clock frequency is disciplined using a first-order
* feedback loop with time constant consistent with the Allan
* intercept of typical computer clocks. During each averaging
* interval the candidate epoch at the end of the longest run is
*/
if (syncnt >= maxrun) {
maxrun = syncnt;
- mepoch = xepoch;
mcount = scount;
+ mepoch = xepoch;
}
xepoch = tepoch;
if (maxrun == 0) {
* The master clock runs at the codec sample frequency of 8000
* Hz, so the intrinsic time resolution is 125 us. The frequency
* resolution ranges from 18 PPM at the minimum averaging
- * interval to 0.12 PPM at the maximum averaging interval. If a
- * frequency update is greater than the maximum MAXFREQ (1.5 or
- * 187.5 PPM), the hysteresis counter is decremented and the
- * update is ignored. Otherwise the frequency is adjusted, but
- * clamped to the maximum. If the update is greater than half
- * the maximum, the hysteresis counter is incremented. If the
- * counter increments to +3, the averaging interval is doubled
- * and the counter set to zero; if it decrements to -3, the
- * interval is halved and the counter set to zero.
+ * interval of 8 s to 0.12 PPM at the maximum interval of 1024
+ * s. An offset update is determined at the end of the longest
+ * run in each averaging interval. The frequency adjustment is
+ * computed from the difference between offset updates and the
+ * interval between them.
+ *
+ * The maximum frequency adjustment ranges from 187 PPM at the
+ * minimum interval to 1.5 PPM at the maximum. If the adjustment
+ * exceeds the maximum, the update is discarded and the
+ * hysteresis counter is decremented. Otherwise, the frequency
+ * is incremented by the adjustment, but clamped to the maximum
+ * 187.5 PPM. If the update is less than half the maximum, the
+ * hysteresis counter is incremented. If the counter increments
+ * to +3, the averaging interval is doubled and the counter set
+ * to zero; if it decrements to -3, the interval is halved and
+ * the counter set to zero.
*/
dtemp = (mepoch - zepoch) % SECOND;
if (up->status & FGATE) {
if (abs(dtemp) < MAXFREQ * MINAVG) {
- up->freq += (dtemp / 2) / ((mcount - zcount) *
+ up->freq += (dtemp / 2.) / ((mcount - zcount) *
FCONST);
if (up->freq > MAXFREQ)
up->freq = MAXFREQ;
printf("%s\n", tbuf);
#endif /* DEBUG */
}
+
+ /*
+ * This is a valid update; set up for the next interval.
+ */
up->status |= FGATE;
zepoch = mepoch;
zcount = mcount;
up = (struct wwvunit *)pp->unitptr;
/*
- * Sample the minute sync pulse energy at epoch 800 for both the
+ * Find the maximum minute sync pulse energy for both the
* WWV and WWVH stations. This will be used later for channel
- * and station mitigation. Note that the seconds epoch is set
- * here well before the end of the second to make sure we never
- * set the epoch backwards.
+ * and station mitigation. Also set the seconds epoch at 800 ms
+ * well before the end of the second to make sure we never set
+ * the epoch backwards.
*/
- if (up->rphase == 800 * MS) {
- up->repoch = up->yepoch;
- cp = &up->mitig[up->achan];
+ cp = &up->mitig[up->achan];
+ if (cp->wwv.amp > cp->wwv.syneng)
cp->wwv.syneng = cp->wwv.amp;
+ if (cp->wwvh.amp > cp->wwvh.syneng)
cp->wwvh.syneng = cp->wwvh.amp;
- }
+ if (up->rphase == 800 * MS)
+ up->repoch = up->yepoch;
/*
* Sample the I and Q data channels at epoch 200 ms. Use the
/*
* Use the signal amplitude at epoch 15 ms as the noise floor.
- * This give a guard time of +-15 ms from the beginning of the
+ * This gives a guard time of +-15 ms from the beginning of the
* second until the pulse rises at 30 ms. There is a compromise
* here; we want to delay the sample as long as possible to give
* the radio time to change frequency and the AGC to stabilize,
sigmin = sigzer = sigone = up->irig;
/*
- * The zero bit is defined as the maximum data signal over the
- * interval 190-210 ms. Keep this around until the end of the
- * second.
+ * Latch the data signal at 200 ms. Keep this around until the
+ * end of the second.
*/
- else if (up->rphase >= 190 * MS && up->rphase < 210 * MS &&
- up->irig > sigzer)
+ else if (up->rphase == 200 * MS)
sigzer = up->irig;
/*
- * The one bit is defined as the maximum data signal over the
- * interval 490-510 ms. Keep this around until the end of the
- * second.
+ * Latch the data signal at 500 ms. Keep this around until the
+ * end of the second.
*/
- else if (up->rphase >= 490 * MS && up->rphase < 510 * MS &&
- up->irig > sigone)
+ else if (up->rphase == 500 * MS)
sigone = up->irig;
/*
* seconds synch is diddling the epoch. Then, determine the true
* offset and update the median filter in the driver interface.
*
- * Use the energy as the noise to compute the SNR for the pulse.
- * This gives a better measurement than the beginning of the
- * second, especially when returning from the probe channel.
- * This gives a guard time of 30 ms from the decay of the
- * longest pulse to the rise of the next pulse.
+ * Use the energy at the end of the second as the noise to
+ * compute the SNR for the data pulse. This gives a better
+ * measurement than the beginning of the second, especially when
+ * returning from the probe channel. This gives a guard time of
+ * 30 ms from the decay of the longest pulse to the rise of the
+ * next pulse.
*/
up->rphase++;
if (up->mphase % SECOND == up->repoch) {
if (up->errcnt > MAXERR)
up->alarm |= LOWERR;
wwv_gain(peer);
+ cp = &up->mitig[up->achan];
+ cp->wwv.syneng = 0;
+ cp->wwvh.syneng = 0;
up->rphase = 0;
}
}
* QSY back to the data channel. Note that the actions commented
* here happen at the end of the second numbered as shown.
*
- * At the end of second 0 save the minute sync pulse maximum
- * amplitude previously latched at 800 ms.
+ * At the end of second 0 save the minute sync amplitude latched
+ * at 800 ms as the signal later used to calculate the SNR.
*/
case SYNC2: /* 0 */
cp = &up->mitig[up->achan];
break;
/*
- * At the end of second 1 measure the minute sync pulse
- * minimum amplitude and calculate the SNR. If the minute sync
- * pulse and SNR are above threshold and the data pulse
+ * At the end of second 1 use the minute sync amplitude latched
+ * at 800 ms as the noise to calculate the SNR. If the minute
+ * sync pulse and SNR are above threshold and the data pulse
* amplitude and SNR are above thresold, shift a 1 into the
* station reachability register; otherwise, shift a 0. The
* number of 1 bits in the last six intervals is a component of
- * the channel metric computed by the mitigation routine.
+ * the channel metric computed by the wwv_metric() routine.
* Finally, QSY back to the data channel.
*/
case SYNC3: /* 1 */
* WWV station
*/
sp = &cp->wwv;
- sp->synsnr = wwv_snr(sp->synmax, sp->syneng);
+ sp->synsnr = wwv_snr(sp->synmax, sp->amp);
sp->reach <<= 1;
if (sp->reach & (1 << AMAX))
sp->count--;
* WWVH station
*/
rp = &cp->wwvh;
- rp->synsnr = wwv_snr(rp->synmax, rp->syneng);
+ rp->synsnr = wwv_snr(rp->synmax, rp->amp);
rp->reach <<= 1;
if (rp->reach & (1 << AMAX))
rp->count--;
up->errcnt = up->digcnt = up->alarm = 0;
/*
- * We now begin the minute scan. Before first
- * synchronizing to a station, reset and step to the
- * next channel immediately if no station has been
- * heard. Reset and step after the DATA timeout (4 min)
- * if a station has been heard, but too few good data
- * bits have been found. In any case, reset and step
- * after the SYNCH timeout (30 min).
- *
- * After synchronizing to a station, reset and step
- * after the PANIC timeout (2 days).
+ * We now begin the minute scan. If not yet synchronized
+ * to a station, restart if the units digit has not been
+ * found within the DATA timeout (15 m) or if not
+ * synchronized within the SYNCH timeout (40 m). After
+ * synchronizing to a station, restart if no stations
+ * are found within the PANIC timeout (2 days).
*/
- if (!(up->status & INSYNC)) {
- if (!wwv_newchan(peer)) {
- wwv_newgame(peer, DCHAN);
+ if (up->status & INSYNC) {
+ if (up->watch > PANIC) {
+ wwv_newgame(peer);
return;
}
- if (up->watch > DATA && !(up->status & DSYNC)) {
- wwv_newgame(peer, DCHAN);
- return;
+ } else {
+ if (!(up->status & DSYNC)) {
+ if (up->watch > DATA) {
+ wwv_newgame(peer);
+ return;
+ }
}
if (up->watch > SYNCH) {
- wwv_newgame(peer, DCHAN);
- return;
- }
- } else {
- wwv_newchan(peer);
- if (up->watch > PANIC) {
- wwv_newgame(peer, DCHAN);
+ wwv_newgame(peer);
return;
}
}
+ wwv_newchan(peer);
#ifdef ICOM
if (up->fd_icom > 0)
wwv_qsy(peer, up->dchan);
/* fall through */
case MSCBIT: /* 2-3, 50, 56-57 */
- if (bitvec[nsec] > BTHR)
+ if (bitvec[nsec] > BTHR) {
+ if (!(up->misc & arg))
+ up->alarm |= CMPERR;
up->misc |= arg;
- else if (bitvec[nsec] < -BTHR)
+ } else if (bitvec[nsec] < -BTHR) {
+ if (up->misc & arg)
+ up->alarm |= CMPERR;
up->misc &= ~arg;
- else
+ } else {
up->status |= BGATE;
+ }
break;
/*
* light them back up.
*/
case MSC21: /* 58 */
- if (bitvec[nsec] > BTHR)
+ if (bitvec[nsec] > BTHR) {
+ if (!(up->misc & arg))
+ up->alarm |= CMPERR;
up->misc |= arg;
- else if (bitvec[nsec] < -BTHR)
+ } else if (bitvec[nsec] < -BTHR) {
+ if (up->misc & arg)
+ up->alarm |= CMPERR;
up->misc &= ~arg;
- else
+ } else {
up->status |= BGATE;
+ }
up->status &= ~(SELV | SELH);
#ifdef ICOM
if (up->fd_icom > 0) {
* down, so the data pulse is unusable as quality metric. If
* LEPSEC is set on the last minute of 30 June or 31 December,
* the transmitter and receiver insert an extra second (60) in
- * the timescale and the minute sync repeats the second. Once we
- * tested this wrinkle at intervals of about 18 months, but
- * strangely enough a leap has not been declared since the end
- * of 1998.
+ * the timescale and the minute sync repeats the second. Once
+ * leaps occurred at intervals of about 18 months, but the last
+ * leap before the most recent leap in 1995 was in 1998.
*/
case MIN1: /* 59 */
if (up->status & LEPSEC)
* Correlate digit vector with each BCD coefficient vector. If
* any BCD digit bit is bad, consider all bits a miss. Until the
* minute units digit has been resolved, don't to anything else.
- */
+ * Note the SNR is calculated as the ratio of the largest
+ * likelihood value to the next largest likelihood value.
+ */
mldigit = 0;
topmax = nxtmax = -MAXAMP;
for (i = 0; tab[i][0] != 0; i++) {
* The current maximum likelihood digit is compared to the last
* maximum likelihood digit. If different, the compare counter
* and maximum likelihood digit are reset. When the compare
- * counter reaches the BCMP (5) threshold, the digit is assumed
+ * counter reaches the BCMP threshold (3), the digit is assumed
* correct. When the compare counter of all nine digits have
* reached threshold, the clock is assumed correct.
*
*/
if (minute != 1440)
return;
+
minute = 0;
while (carry(&up->decvec[HR]) != 0); /* advance to minute 0 */
while (carry(&up->decvec[HR + 1]) != 0);
*/
if (day != (isleap ? 365 : 366))
return;
+
day = 1;
while (carry(&up->decvec[DA]) != 1); /* advance to day 1 */
while (carry(&up->decvec[DA + 1]) != 0);
* 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 we
- * simply cap at MAXSNR dB.
+ * simply cap at MAXSNR dB (40).
*/
if (signal <= 0) {
rval = 0;
*
* The radio actually appears to have ten channels, one channel for each
* of five frequencies and each of two stations (WWV and WWVH), although
- * if not tunable only the 15 MHz channels appear live. While the radio
+ * if not tunable only the DCHAN channel appears live. 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 frequency in order to search for minute sync pulse and data
* pulse amplitude and then to the highest frequency.
*
* The routine performs an important squelch function to keep dirty data
- * from polluting the integrators. During acquisition and until the
- * clock is synchronized, the signal metric must be at least MTR (13);
- * after that the metrict must be at least TTHR (50). If either of these
- * is not true, the station select bits are cleared so the second sync
- * is disabled and the data bit integrators averaged to a miss.
+ * from polluting the integrators. In order to consider a station valid,
+ * the metric must be at least MTHR (13); otherwise, the station select
+ * bits are cleared so the second sync is disabled and the data bit
+ * integrators averaged to a miss.
*/
static int
wwv_newchan(
}
/*
- * If the strongest signal is less than threshold, we are
- * beneath the waves. if the first second in the minute has been
- * found, tune to 15 MHz and wait for sunrise. If strongest
- * signal is greater than threshold, tune to that frequency and
- * the transmitter.
+ * If the strongest signal is less than the MTHR threshold (13),
+ * we are beneath the waves, so squelch the second sync. If the
+ * strongest signal is greater than the threshold, tune to that
+ * frequency and transmitter QTH.
*/
if (rank < MTHR) {
- if (up->status & MSYNC) {
- up->dchan = DCHAN;
- sp = &up->mitig[DCHAN].wwv;
- up->sptr = sp ;
- memcpy(&pp->refid, "NONE", 4);
- up->status |= sp->select & (SELV | SELH);
- } else {
- memcpy(&pp->refid, "SCAN", 4);
- up->status &= ~(SELV | SELH);
- }
- } else {
- up->dchan = j;
- up->sptr = sp;
- memcpy(&pp->refid, sp->refid, 4);
- up->status |= sp->select & (SELV | SELH);
+ up->dchan = (up->dchan + 1) % NCHAN;
+ up->status &= ~(SELV | SELH);
+ return (FALSE);
}
+ up->dchan = j;
+ up->status |= SELV | SELH;
+ up->sptr = sp;
+ memcpy(&pp->refid, sp->refid, 4);
peer->refid = pp->refid;
- return (up->status & (SELV | SELH));
+ return (TRUE);
}
*
* There are four conditions resulting in a new game:
*
- * 1 During initial acquisition (MSYNC dark) going 5 minutes (ACQSN)
+ * 1 During initial acquisition (MSYNC dark) going 6 minutes (ACQSN)
* without reliably finding the minute pulse (MSYNC lit).
*
- * 2 After finding the minute pulse (MSYNC lit), going 5 minutes
+ * 2 After finding the minute pulse (MSYNC lit), going 15 minutes
* (DATA) without finding the unit seconds digit.
*
- * 3 After finding good data (DATA lit), going more than 30 minutes
+ * 3 After finding good data (DATA lit), going more than 40 minutes
* (SYNCH) without finding station sync (INSYNC lit).
*
- * 4 After finding station sync (INSYNC lit), going more than two
- * days (PANIC) without finding station sync again.
+ * 4 After finding station sync (INSYNC lit), going more than 2 days
+ * (PANIC) without finding any station.
*/
static void
wwv_newgame(
- struct peer *peer, /* peer structure pointer */
- int chan /* start channel scan */
+ struct peer *peer /* peer structure pointer */
)
{
struct refclockproc *pp;
cp->wwvh.select = SELH;
sprintf(cp->wwvh.refid, "WH%.0f", floor(qsy[i]));
}
+ up->dchan = (DCHAN + NCHAN - 1) % NCHAN;;
wwv_newchan(peer);
- up->dchan = up->achan = up->schan = chan;
+ up->achan = up->schan = up->dchan;
#ifdef ICOM
if (up->fd_icom > 0)
- wwv_qsy(peer, chan);
+ wwv_qsy(peer, up->dchan);
#endif /* ICOM */
}
/*
* wwv_gain - adjust codec gain
*
- * This routine is called at the end of each second. It counts the
- * number of signal clips above the MAXAMP threshold during the previous
- * second. If there are no clips, the gain is bumped up; if too many
- * clips, it is bumped down. The decoder is relatively insensitive to
- * amplitude, so this crudity works just fine. The input port is set and
- * the error flag is cleared, mostly to be ornery.
+ * This routine is called at the end of each second. During the second
+ * the number of signal clips above the MAXAMP threshold (6000). If
+ * there are no clips, the gain is bumped up; if there are more than
+ * MAXCLP clips (100), it is bumped down. The decoder is relatively
+ * insensitive to amplitude, so this crudity works just peachy. The
+ * input port is set and the error flag is cleared, mostly to be ornery.
*/
static void
wwv_gain(