1 /* $NetBSD: refclock_wwvb.c,v 1.2 2003/12/04 16:23:38 drochner Exp $ */
4 * refclock_wwvb - clock driver for Spectracom WWVB and GPS receivers
11 #if defined(REFCLOCK) && defined(CLOCK_SPECTRACOM)
15 #include "ntp_refclock.h"
16 #include "ntp_calendar.h"
17 #include "ntp_stdlib.h"
23 * This driver supports the Spectracom Model 8170 and Netclock/2 WWVB
24 * Synchronized Clocks and the Netclock/GPS Master Clock. Both the WWVB
25 * and GPS clocks have proven reliable sources of time; however, the
26 * WWVB clocks have proven vulnerable to high ambient conductive RF
27 * interference. The claimed accuracy of the WWVB clocks is 100 us
28 * relative to the broadcast signal, while the claimed accuracy of the
29 * GPS clock is 50 ns; however, in most cases the actual accuracy is
30 * limited by the resolution of the timecode and the latencies of the
31 * serial interface and operating system.
33 * The WWVB and GPS clocks should be configured for 24-hour display,
34 * AUTO DST off, time zone 0 (UTC), data format 0 or 2 (see below) and
35 * baud rate 9600. If the clock is to used as the source for the IRIG
36 * Audio Decoder (refclock_irig.c in this distribution), it should be
37 * configured for AM IRIG output and IRIG format 1 (IRIG B with
38 * signature control). The GPS clock can be configured either to respond
39 * to a 'T' poll character or left running continuously.
41 * There are two timecode formats used by these clocks. Format 0, which
42 * is available with both the Netclock/2 and 8170, and format 2, which
43 * is available only with the Netclock/2, specially modified 8170 and
46 * Format 0 (22 ASCII printing characters):
48 * <cr><lf>i ddd hh:mm:ss TZ=zz<cr><lf>
50 * on-time = first <cr>
51 * hh:mm:ss = hours, minutes, seconds
52 * i = synchronization flag (' ' = in synch, '?' = out of synch)
54 * The alarm condition is indicated by other than ' ' at a, which occurs
55 * during initial synchronization and when received signal is lost for
58 * Format 2 (24 ASCII printing characters):
60 * <cr><lf>iqyy ddd hh:mm:ss.fff ld
63 * i = synchronization flag (' ' = in synch, '?' = out of synch)
64 * q = quality indicator (' ' = locked, 'A'...'D' = unlocked)
65 * yy = year (as broadcast)
67 * hh:mm:ss.fff = hours, minutes, seconds, milliseconds
69 * The alarm condition is indicated by other than ' ' at a, which occurs
70 * during initial synchronization and when received signal is lost for
71 * about ten hours. The unlock condition is indicated by other than ' '
74 * The q is normally ' ' when the time error is less than 1 ms and a
75 * character in the set 'A'...'D' when the time error is less than 10,
76 * 100, 500 and greater than 500 ms respectively. The l is normally ' ',
77 * but is set to 'L' early in the month of an upcoming UTC leap second
78 * and reset to ' ' on the first day of the following month. The d is
79 * set to 'S' for standard time 'I' on the day preceding a switch to
80 * daylight time, 'D' for daylight time and 'O' on the day preceding a
81 * switch to standard time. The start bit of the first <cr> is
82 * synchronized to the indicated time as returned.
84 * This driver does not need to be told which format is in use - it
85 * figures out which one from the length of the message. The driver
86 * makes no attempt to correct for the intrinsic jitter of the radio
87 * itself, which is a known problem with the older radios.
91 * This driver can retrieve a table of quality data maintained
92 * internally by the Netclock/2 clock. If flag4 of the fudge
93 * configuration command is set to 1, the driver will retrieve this
94 * table and write it to the clockstats file when the first timecode
95 * message of a new day is received.
97 * PPS calibration fudge time 1: format 0 .003134, format 2 .004034
100 * Interface definitions
102 #define DEVICE "/dev/wwvb%d" /* device name and unit */
103 #define SPEED232 B9600 /* uart speed (9600 baud) */
104 #define PRECISION (-13) /* precision assumed (about 100 us) */
105 #define REFID "WWVB" /* reference ID */
106 #define DESCRIPTION "Spectracom WWVB/GPS Receiver" /* WRU */
108 #define LENWWVB0 22 /* format 0 timecode length */
109 #define LENWWVB1 22 /* format 1 timecode length */
110 #define LENWWVB2 24 /* format 2 timecode length */
111 #define LENWWVB3 29 /* format 3 timecode length */
112 #define MONLIN 15 /* number of monitoring lines */
115 * WWVB unit control structure
118 l_fp laststamp
; /* last receive timestamp */
119 u_char lasthour
; /* last hour (for monitor) */
120 u_char linect
; /* count ignored lines (for monitor */
124 * Function prototypes
126 static int wwvb_start
P((int, struct peer
*));
127 static void wwvb_shutdown
P((int, struct peer
*));
128 static void wwvb_receive
P((struct recvbuf
*));
129 static void wwvb_poll
P((int, struct peer
*));
130 static void wwvb_timer
P((int, struct peer
*));
135 struct refclock refclock_wwvb
= {
136 wwvb_start
, /* start up driver */
137 wwvb_shutdown
, /* shut down driver */
138 wwvb_poll
, /* transmit poll message */
139 noentry
, /* not used (old wwvb_control) */
140 noentry
, /* initialize driver (not used) */
141 noentry
, /* not used (old wwvb_buginfo) */
142 wwvb_timer
/* called once per second */
147 * wwvb_start - open the devices and initialize data for processing
155 register struct wwvbunit
*up
;
156 struct refclockproc
*pp
;
161 * Open serial port. Use CLK line discipline, if available.
163 sprintf(device
, DEVICE
, unit
);
164 if (!(fd
= refclock_open(device
, SPEED232
, LDISC_CLK
)))
168 * Allocate and initialize unit structure
170 if (!(up
= (struct wwvbunit
*)
171 emalloc(sizeof(struct wwvbunit
)))) {
175 memset((char *)up
, 0, sizeof(struct wwvbunit
));
177 pp
->unitptr
= (caddr_t
)up
;
178 pp
->io
.clock_recv
= wwvb_receive
;
179 pp
->io
.srcclock
= (caddr_t
)peer
;
182 if (!io_addclock(&pp
->io
)) {
189 * Initialize miscellaneous variables
191 peer
->precision
= PRECISION
;
192 pp
->clockdesc
= DESCRIPTION
;
193 memcpy((char *)&pp
->refid
, REFID
, 4);
199 * wwvb_shutdown - shut down the clock
207 register struct wwvbunit
*up
;
208 struct refclockproc
*pp
;
211 up
= (struct wwvbunit
*)pp
->unitptr
;
212 io_closeclock(&pp
->io
);
218 * wwvb_receive - receive data from the serial interface
222 struct recvbuf
*rbufp
226 struct refclockproc
*pp
;
229 l_fp trtmp
; /* arrival timestamp */
230 int tz
; /* time zone */
231 int day
, month
; /* ddd conversion */
232 int temp
; /* int temp */
233 char syncchar
; /* synchronization indicator */
234 char qualchar
; /* quality indicator */
235 char leapchar
; /* leap indicator */
236 char dstchar
; /* daylight/standard indicator */
237 char tmpchar
; /* trashbin */
240 * Initialize pointers and read the timecode and timestamp
242 peer
= (struct peer
*)rbufp
->recv_srcclock
;
244 up
= (struct wwvbunit
*)pp
->unitptr
;
245 temp
= refclock_gtlin(rbufp
, pp
->a_lastcode
, BMAX
, &trtmp
);
248 * Note we get a buffer and timestamp for both a <cr> and <lf>,
249 * but only the <cr> timestamp is retained. Note: in format 0 on
250 * a Netclock/2 or upgraded 8170 the start bit is delayed 100
251 * +-50 us relative to the pps; however, on an unmodified 8170
252 * the start bit can be delayed up to 10 ms. In format 2 the
253 * reading precision is only to the millisecond. Thus, unless
254 * you have a PPS gadget and don't have to have the year, format
255 * 0 provides the lowest jitter.
258 up
->laststamp
= trtmp
;
262 pp
->lastrec
= up
->laststamp
;
265 * We get down to business, check the timecode format and decode
266 * its contents. This code uses the timecode length to determine
267 * format 0, 2 or 3. If the timecode has invalid length or is
268 * not in proper format, we declare bad format and exit.
270 syncchar
= qualchar
= leapchar
= dstchar
= ' ';
272 switch (pp
->lencode
) {
277 * Timecode format 0: "I ddd hh:mm:ss DTZ=nn"
279 if (sscanf(pp
->a_lastcode
,
280 "%c %3d %2d:%2d:%2d%c%cTZ=%2d",
281 &syncchar
, &pp
->day
, &pp
->hour
, &pp
->minute
,
282 &pp
->second
, &tmpchar
, &dstchar
, &tz
) == 8)
289 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */
290 if (sscanf(pp
->a_lastcode
,
291 "%c%c %2d %3d %2d:%2d:%2d.%3ld %c",
292 &syncchar
, &qualchar
, &pp
->year
, &pp
->day
,
293 &pp
->hour
, &pp
->minute
, &pp
->second
, &pp
->nsec
,
301 * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#"
303 if (sscanf(pp
->a_lastcode
,
304 "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c",
305 &syncchar
, &pp
->year
, &month
, &day
, &pp
->hour
,
306 &pp
->minute
, &pp
->second
, &dstchar
, &leapchar
) == 8)
308 pp
->day
= ymd2yd(pp
->year
, month
, day
);
316 * Unknown format: If dumping internal table, record
317 * stats; otherwise, declare bad format.
319 if (up
->linect
> 0) {
321 record_clock_stats(&peer
->srcadr
,
324 refclock_report(peer
, CEVNT_BADREPLY
);
330 * Decode synchronization, quality and leap characters. If
331 * unsynchronized, set the leap bits accordingly and exit.
332 * Otherwise, set the leap bits according to the leap character.
333 * Once synchronized, the dispersion depends only on the
340 pp
->lastref
= pp
->lastrec
;
356 pp
->disp
= MAXDISPERSE
;
360 pp
->disp
= MAXDISPERSE
;
361 refclock_report(peer
, CEVNT_BADREPLY
);
365 pp
->leap
= LEAP_NOTINSYNC
;
366 else if (leapchar
== 'L')
367 pp
->leap
= LEAP_ADDSECOND
;
369 pp
->leap
= LEAP_NOWARNING
;
372 * Process the new sample in the median filter and determine the
373 * timecode timestamp.
375 if (!refclock_process(pp
))
376 refclock_report(peer
, CEVNT_BADTIME
);
377 if (peer
->disp
> MAXDISTANCE
)
378 refclock_receive(peer
);
383 * wwvb_timer - called once per second by the transmit procedure
391 register struct wwvbunit
*up
;
392 struct refclockproc
*pp
;
393 char pollchar
; /* character sent to clock */
396 * Time to poll the clock. The Spectracom clock responds to a
397 * 'T' by returning a timecode in the format(s) specified above.
398 * Note there is no checking on state, since this may not be the
399 * only customer reading the clock. Only one customer need poll
400 * the clock; all others just listen in.
403 up
= (struct wwvbunit
*)pp
->unitptr
;
408 if (write(pp
->io
.fd
, &pollchar
, 1) != 1)
409 refclock_report(peer
, CEVNT_FAULT
);
414 * wwvb_poll - called by the transmit procedure
422 register struct wwvbunit
*up
;
423 struct refclockproc
*pp
;
426 * Sweep up the samples received since the last poll. If none
427 * are received, declare a timeout and keep going.
430 up
= (struct wwvbunit
*)pp
->unitptr
;
434 * If the monitor flag is set (flag4), we dump the internal
435 * quality table at the first timecode beginning the day.
437 if (pp
->sloppyclockflag
& CLK_FLAG4
&& pp
->hour
<
440 up
->lasthour
= pp
->hour
;
443 * Process median filter samples. If none received, declare a
444 * timeout and keep going.
446 if (pp
->coderecv
== pp
->codeproc
) {
447 refclock_report(peer
, CEVNT_TIMEOUT
);
450 refclock_receive(peer
);
451 record_clock_stats(&peer
->srcadr
, pp
->a_lastcode
);
454 printf("wwvb: timecode %d %s\n", pp
->lencode
,
460 int refclock_wwvb_bs
;
461 #endif /* REFCLOCK */