* 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
- * Solaris, and with a little help, other workstations with similar
- * codecs or sound cards. In this implementation, only one audio driver
- * and codec can be supported on a single machine.
+ * connected to the audio codec of supported workstation hardware and
+ * operating system. These include Solaris, SunOS, FreeBSD, NetBSD and
+ * Linux. In this implementation, only one audio driver and codec can be
+ * supported on a single machine.
*
* The driver can be compiled to use a Bell 103 compatible modem or
* modem chip to receive the radio signal and demodulate the data.
#define TUNE .001 /* offset for narrow filter (kHz) */
#define DWELL 5 /* minutes in a probe cycle */
#define NCHAN 3 /* number of channels */
+#undef NSTAGE
#define NSTAGE 3 /* number of integrator stages */
#endif /* ICOM */
#define BURST 11 /* max characters per burst */
#define MINCHAR 9 /* min characters per burst */
#define MINDIST 28 /* min burst distance (of 40) */
+#define MINBURST 4 /* min bursts in minute */
#define MINSYNC 8 /* min sync distance (of 16) */
#define MINSTAMP 20 /* min timestamps (of 60) */
+#define METRIC 50. /* min channel metric */
#define PANIC (4 * 1440) /* panic restart */
/*
* Hex extension codes (>= 16)
*/
-#define HEX_MISS 16 /* miss */
-#define HEX_SOFT 17 /* soft error */
-#define HEX_HARD 18 /* hard error */
+#define HEX_MISS 16 /* miss _ */
+#define HEX_SOFT 17 /* soft error * */
+#define HEX_HARD 18 /* hard error = */
/*
* Status bits (status)
#define AFORMAT 0x0020 /* invalid format A data */
#define DECODE 0x0040 /* invalid data decode */
#define STAMP 0x0080 /* too few timestamps */
-#define INSECOND 0x0100 /* valid A frame */
-#define INYEAR 0x0200 /* valid B frame */
+#define AVALID 0x0100 /* valid A frame */
+#define BVALID 0x0200 /* valid B frame */
#define INSYNC 0x0400 /* clock synchronized */
/*
#define TSPERR 0x08 /* insufficient data */
#ifdef HAVE_AUDIO
+/*
+ * Maximum likelihood UART structure. There are eight of these
+ * corresponding to the number of phases.
+ */
struct surv {
double shift[12]; /* mark register */
double es_max, es_min; /* max/min envelope signals */
#endif /* HAVE_AUDIO */
#ifdef ICOM
+/*
+ * CHU station structure. There are three of these corresponding to the
+ * three frequencies.
+ */
struct xmtr {
- int integ[NSTAGE]; /* circular integrator */
+ double integ[NSTAGE]; /* circular integrator */
+ double metric; /* integrator sum */
int iptr; /* integrator pointer */
- int metric; /* integrator sum */
+ int probe; /* dwells since last probe */
};
#endif /* ICOM */
l_fp charstamp; /* character time as a l_fp */
int errflg; /* error flags */
int status; /* status bits */
- char ident[10]; /* transmitter frequency */
+ char ident[5]; /* station ID and channel */
#ifdef ICOM
int fd_icom; /* ICOM file descriptor */
int chan; /* data channel */
- int pchan; /* probe channel */
int achan; /* active channel */
- int dwell; /* dwell minutes at current frequency */
+ int dwell; /* dwell cycle */
struct xmtr xmtr[NCHAN]; /* station metric */
#endif /* ICOM */
int ndx; /* buffer start index */
int prevsec; /* previous burst second */
int burdist; /* burst distance */
- int mindist; /* minimum distance */
int syndist; /* sync distance */
int burstcnt; /* format A bursts this minute */
static void chu_a P((struct peer *, int));
static void chu_b P((struct peer *, int));
static int chu_dist P((int, int));
-static int chu_major P((struct peer *));
+static double chu_major P((struct peer *));
#ifdef HAVE_AUDIO
static void chu_uart P((struct surv *, double));
static void chu_rf P((struct peer *, double));
static void chu_audio_receive P((struct recvbuf *rbufp));
#endif /* HAVE_AUDIO */
#ifdef ICOM
-static void chu_newchan P((struct peer *, int));
+static int chu_newchan P((struct peer *, double));
#endif /* ICOM */
static void chu_serial_receive P((struct recvbuf *rbufp));
/*
* Global variables
*/
-static char hexchar[] = "0123456789abcdef_-=";
+static char hexchar[] = "0123456789abcdef_*=";
#ifdef ICOM
/*
temp);
}
if (up->fd_icom > 0) {
- if (icom_freq(up->fd_icom, peer->ttl & 0x7f, qsy[1] +
- TUNE) != 0) {
+ if (chu_newchan(peer, 0) != 0) {
NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT)
- msyslog(LOG_ERR,
+ msyslog(LOG_NOTICE,
"icom: radio not found");
up->errflg = CEVNT_FAULT;
close(up->fd_icom);
free(up);
}
+
/*
* chu_receive - receive data from the audio or serial device
*/
#endif /* HAVE_AUDIO */
}
-#ifdef HAVE_AUDIO
+#ifdef HAVE_AUDIO
/*
* chu_audio_receive - receive data from the audio device
*/
* In a format B burst, a character is considered valid only if
* the first occurrence matches the last occurrence. The burst
* is considered valid only if all characters are valid; that
- * is, only if the distance is 40.
+ * is, only if the distance is 40. Note that once a valid frame
+ * has been found errors are ignored.
*/
sprintf(tbuf, "chuB %04x %2d %2d ", up->status, nchar,
-up->burdist);
up->status |= BFRAME;
return;
}
- up->status |= INYEAR;
+ up->status |= BVALID;
/*
* Convert the burst data to internal format. If this succeeds,
up->decode[i][(up->cbuf[j] >> 4) & 0xf]++;
i++;
}
- up->status |= INSECOND;
+ up->status |= AVALID;
up->burstcnt++;
}
struct chuunit *up;
char synchar, qual, leapchar;
int minset;
- int temp;
+ double dtemp;
pp = peer->procptr;
up = (struct chuunit *)pp->unitptr;
/*
* Process the last burst, if still in the burst buffer.
- * Don't mess with anything if nothing has been heard.
+ * Don't mess with anything if nothing has been heard. If the
+ * minute contains a valid A frame and valid B frame, assume
+ * synchronized; however, believe the time only if within metric
+ * threshold. Note the quality indicator is only for
+ * diagnostics; the data are used only if in sync and above
+ * metric threshold.
*/
chu_burst(peer);
if (up->burstcnt == 0) {
#endif /* ICOM */
return;
}
- temp = chu_major(peer);
- if (up->status & INYEAR)
- up->status |= INSYNC;
+ dtemp = chu_major(peer);
qual = 0;
if (up->status & (BFRAME | AFRAME))
qual |= SYNERR;
qual |= DECERR;
if (up->status & STAMP)
qual |= TSPERR;
+ if (up->status & AVALID && up->status & BVALID)
+ up->status |= INSYNC;
synchar = leapchar = ' ';
if (!(up->status & INSYNC)) {
pp->leap = LEAP_NOTINSYNC;
#ifdef HAVE_AUDIO
if (up->fd_audio)
sprintf(pp->a_lastcode,
- "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %d %s %d %d %d %d",
+ "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %d %s %.0f %d",
synchar, qual, pp->year, pp->day, pp->hour,
pp->minute, pp->second, leapchar, up->dst, up->dut,
- minset, up->gain, up->ident, up->tai, up->burstcnt,
- up->mindist, temp);
+ minset, up->gain, up->ident, dtemp, up->ntstamp);
else
sprintf(pp->a_lastcode,
- "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %s %d %d %d %d",
+ "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %s %.0f %d",
synchar, qual, pp->year, pp->day, pp->hour,
pp->minute, pp->second, leapchar, up->dst, up->dut,
- minset, up->ident, up->tai, up->burstcnt,
- up->mindist, temp);
+ minset, up->ident, dtemp, up->ntstamp);
#else
sprintf(pp->a_lastcode,
- "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %s %d %d %d %d",
+ "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %s %.0f %d",
synchar, qual, pp->year, pp->day, pp->hour, pp->minute,
pp->second, leapchar, up->dst, up->dut, minset, up->ident,
- up->tai, up->burstcnt, up->mindist, temp);
+ dtemp, up->ntstamp);
#endif /* HAVE_AUDIO */
pp->lencode = strlen(pp->a_lastcode);
* If timestamps have been stuffed, the timecode is ipso fatso
* correct and can be selected to discipline the clock.
*/
- if (temp > 0 && qual == 0) {
+ if (up->status & INSYNC && dtemp > METRIC) {
refclock_receive(peer);
record_clock_stats(&peer->srcadr, pp->a_lastcode);
} else if (pp->sloppyclockflag & CLK_FLAG4) {
pp->a_lastcode);
#endif
#ifdef ICOM
- chu_newchan(peer, temp);
+ chu_newchan(peer, dtemp);
#endif /* ICOM */
chu_clear(peer);
if (up->errflg)
/*
* chu_major - majority decoder
*/
-static int
+static double
chu_major(
struct peer *peer /* peer structure pointer */
)
u_char code[11]; /* decoded timecode */
l_fp offset; /* l_fp temps */
+ int mindist; /* minimum distance */
int val1, val2; /* maximum distance */
int synchar; /* stray cat */
int temp;
* and the corresponding digit is the maximumn likelihood
* candidate. If the distance is zero, assume a miss '_'; if the
* distance is not more than half the total number of
- * occurences, assume a soft error '-'; if two different digits
+ * occurences, assume a soft error '*'; if two different digits
* with the same distance are found, assume a hard error '='.
* These will later cause a format error when the timecode is
* interpreted. The decoding distance is defined as the minimum
* distance over the first nine digits. The tenth digit varies
* over the seconds, so we don't count it.
*/
- up->mindist = 16;
+ mindist = 16;
for (i = 0; i < 9; i++) {
val1 = val2 = 0;
k = 0;
code[i] = HEX_SOFT;
else
code[i] = k;
- if (val1 < up->mindist)
- up->mindist = val1;
+ if (val1 < mindist)
+ mindist = val1;
code[i] = hexchar[code[i]];
}
code[i] = 0;
/*
- * A valid timecode requires at least three bursts and a
- * decoding distance greater than half the total number of
- * occurences. A valid timecode also requires at least 20 valid
- * timestamps.
+ * A valid timecode requires a minimum distance at least half
+ * the total number of occurences. A valid timecode also
+ * requires at least 20 valid timestamps.
*/
- if (up->burstcnt < 3 || up->mindist <= up->burstcnt)
+ if (up->burstcnt < MINBURST || mindist < up->burstcnt)
up->status |= DECODE;
if (up->ntstamp < MINSTAMP)
up->status |= STAMP;
refclock_process_offset(pp, offset, up->tstamp[i],
FUDGE + pp->fudgetime1);
pp->lastref = up->timestamp;
- return (i);
+ return (mindist * 100. / (2. * up->burstcnt));
}
* Clear stuff for the minute.
*/
up->ndx = up->prevsec = 0;
- up->burstcnt = up->mindist = up->ntstamp = 0;
- up->status &= INSYNC | INYEAR | INSECOND;
- up->burstcnt = 0;
+ up->burstcnt = up->ntstamp = 0;
+ up->status &= INSYNC;
for (i = 0; i < 20; i++) {
for (j = 0; j < 16; j++)
up->decode[i][j] = 0;
#ifdef ICOM
/*
- * chu_newchan - called once per minute find the best channel
+ * chu_newchan - called once per minute to find the best channel;
+ * returns zero on success, nonzero if ICOM error.
*/
-void
+int
chu_newchan(
struct peer *peer,
- int met
+ double met
)
{
struct chuunit *up;
struct refclockproc *pp;
struct xmtr *sp;
- int metric;
- int i, j;
char tbuf[80]; /* trace buffer */
+ int rval;
+ double metric;
+ int i, j;
pp = peer->procptr;
up = (struct chuunit *)pp->unitptr;
/*
- * Before INSECOND is set we dwell two minutes on each channel
- * in turn. The seconds are synchronized when the first burst
- * has been successfully decoded. After that the channels are
- * evaluated and the winner becomes the data channel. Every five
- * minutes we switch to a probe channel which rotates over all
- * three frequencies. The next time the channels are evaluated a
- * new winner may result.
+ * The radio can be tuned to three channels: 0 (3330 kHz), 1
+ * (7335 kHz) and 2 (14670 kHz). There are five one-minute
+ * dwells in each cycle. During the first dwell the radio is
+ * tuned to one of three probe channels; during the remaining
+ * four dwells the radio is tuned to the data channel. The probe
+ * channel is selects as the least recently used. At the end of
+ * each dwell the channel metrics are measured and the highest
+ * one is selected as the data channel.
*/
if (up->fd_icom <= 0)
- return;
+ return (0);
- if (up->burstcnt == 0)
- up->dwell = 0;
sp = &up->xmtr[up->achan];
sp->metric -= sp->integ[sp->iptr];
sp->integ[sp->iptr] = met;
metric = 0;
j = 0;
for (i = 0; i < NCHAN; i++) {
+ up->xmtr[i].probe++;
+ if (i == up->achan)
+ up->xmtr[i].probe = 0;
if (up->xmtr[i].metric < metric)
continue;
metric = up->xmtr[i].metric;
}
if (j != up->chan && metric > 0) {
up->chan = j;
- if (pp->sloppyclockflag & CLK_FLAG4) {
- sprintf(tbuf, "chu: QSY to %.3f MHz metric %d",
- qsy[up->chan], metric);
+ sprintf(tbuf, "chu: QSY to %.3f MHz metric %.0f",
+ qsy[up->chan], metric);
+ if (pp->sloppyclockflag & CLK_FLAG4)
record_clock_stats(&peer->srcadr, tbuf);
#ifdef DEBUG
if (debug)
printf("%s\n", tbuf);
#endif
- }
}
+
+ /*
+ * Start the next dwell. We speed up the initial sync a little.
+ * If not in sync and no bursts were heard the previous dwell,
+ * restart the probe.
+ */
+ rval = 0;
+ if (up->burstcnt == 0 && !(up->status & INSYNC))
+ up->dwell = 0;
#ifdef DEBUG
if (debug)
printf(
- "chu: at %ld dwell %d achan %d metric %d chan %d pchan %d\n",
+ "chu: at %ld dwell %d achan %d metric %.0f chan %d\n",
current_time, up->dwell, up->achan, sp->metric,
- up->chan, up->pchan);
+ up->chan);
#endif
if (up->dwell == 0) {
- if (up->achan == up->pchan)
- up->pchan = (up->pchan + 1) % NCHAN;
- icom_freq(up->fd_icom, peer->ttl & 0x7f,
- qsy[up->pchan] + TUNE);
- up->achan = up->pchan;
- up->pchan = (up->pchan + 1) % NCHAN;
+ rval = 0;
+ for (i = 0; i < NCHAN; i++) {
+ if (up->xmtr[i].probe < rval)
+ continue;
+ rval = up->xmtr[i].probe;
+ up->achan = i;
+ }
+ rval = icom_freq(up->fd_icom, peer->ttl & 0x7f,
+ qsy[up->achan] + TUNE);
+#ifdef DEBUG
+ if (debug)
+ printf("chu: at %ld probe channel %d\n",
+ current_time, up->achan);
+#endif
} else {
if (up->achan != up->chan) {
- icom_freq(up->fd_icom, peer->ttl & 0x7f,
+ rval = icom_freq(up->fd_icom, peer->ttl & 0x7f,
qsy[up->chan] + TUNE);
up->achan = up->chan;
}
sprintf(up->ident, "CHU%d", up->achan);
memcpy(&peer->refid, up->ident, 4);
up->dwell = (up->dwell + 1) % DWELL;
+ return (rval);
}
#endif /* ICOM */