1 /* $NetBSD: refclock_ulink.c,v 1.2 2003/12/04 16:23:37 drochner Exp $ */
4 * refclock_ulink - clock driver for Ultralink WWVB receiver
7 /***********************************************************************
9 * Copyright (c) David L. Mills 1992-1998 *
11 * Permission to use, copy, modify, and distribute this software and *
12 * its documentation for any purpose and without fee is hereby *
13 * granted, provided that the above copyright notice appears in all *
14 * copies and that both the copyright notice and this permission *
15 * notice appear in supporting documentation, and that the name *
16 * University of Delaware not be used in advertising or publicity *
17 * pertaining to distribution of the software without specific, *
18 * written prior permission. The University of Delaware makes no *
19 * representations about the suitability this software for any *
20 * purpose. It is provided "as is" without express or implied *
22 **********************************************************************/
28 #if defined(REFCLOCK) && defined(CLOCK_ULINK)
35 #include "ntp_refclock.h"
36 #include "ntp_stdlib.h"
38 /* This driver supports ultralink Model 320,325,330,331,332 WWVB radios
40 * this driver was based on the refclock_wwvb.c driver
41 * in the ntp distribution.
45 * fudge flag1 0 don't poll clock
46 * 1 send poll character
49 * 99/9/09 j.c.lang original edit's
50 * 99/9/11 j.c.lang changed timecode parse to
51 * match what the radio actually
53 * 99/10/11 j.c.lang added support for continous
54 * time code mode (dipsw2)
55 * 99/11/26 j.c.lang added support for 320 decoder
56 * (taken from Dave Strout's
58 * 99/11/29 j.c.lang added fudge flag 1 to control
60 * 99/12/15 j.c.lang fixed 320 quality flag
61 * 01/02/21 s.l.smith fixed 33x quality flag
62 * added more debugging stuff
63 * updated 33x time code explanation
64 * 04/01/23 frank migge added support for 325 decoder
65 * (tested with ULM325.F)
67 * Questions, bugs, ideas send to:
69 * tcnojl1@earthlink.net
72 * dstrout@linuxfoundry.com
75 * frank.migge@oracle.com
78 * on the Ultralink model 33X decoder Dip switch 2 controls
79 * polled or continous timecode
80 * set fudge flag1 if using polled (needed for model 320 and 325)
81 * dont set fudge flag1 if dip switch 2 is set on model 33x decoder
86 * Interface definitions
88 #define DEVICE "/dev/wwvb%d" /* device name and unit */
89 #define SPEED232 B9600 /* uart speed (9600 baud) */
90 #define PRECISION (-10) /* precision assumed (about 10 ms) */
91 #define REFID "WWVB" /* reference ID */
92 #define DESCRIPTION "Ultralink WWVB Receiver" /* WRU */
94 #define LEN33X 32 /* timecode length Model 33X and 325 */
95 #define LEN320 24 /* timecode length Model 320 */
97 #define SIGLCHAR33x 'S' /* signal strength identifier char 325 */
98 #define SIGLCHAR325 'R' /* signal strength identifier char 33x */
101 * unit control structure
104 u_char tcswitch
; /* timecode switch */
105 l_fp laststamp
; /* last receive timestamp */
109 * Function prototypes
111 static int ulink_start
P((int, struct peer
*));
112 static void ulink_shutdown
P((int, struct peer
*));
113 static void ulink_receive
P((struct recvbuf
*));
114 static void ulink_poll
P((int, struct peer
*));
119 struct refclock refclock_ulink
= {
120 ulink_start
, /* start up driver */
121 ulink_shutdown
, /* shut down driver */
122 ulink_poll
, /* transmit poll message */
123 noentry
, /* not used */
124 noentry
, /* not used */
125 noentry
, /* not used */
131 * ulink_start - open the devices and initialize data for processing
139 register struct ulinkunit
*up
;
140 struct refclockproc
*pp
;
145 * Open serial port. Use CLK line discipline, if available.
147 (void)sprintf(device
, DEVICE
, unit
);
148 if (!(fd
= refclock_open(device
, SPEED232
, LDISC_CLK
)))
152 * Allocate and initialize unit structure
154 if (!(up
= (struct ulinkunit
*)
155 emalloc(sizeof(struct ulinkunit
)))) {
159 memset((char *)up
, 0, sizeof(struct ulinkunit
));
161 pp
->unitptr
= (caddr_t
)up
;
162 pp
->io
.clock_recv
= ulink_receive
;
163 pp
->io
.srcclock
= (caddr_t
)peer
;
166 if (!io_addclock(&pp
->io
)) {
173 * Initialize miscellaneous variables
175 peer
->precision
= PRECISION
;
176 peer
->burst
= NSTAGE
;
177 pp
->clockdesc
= DESCRIPTION
;
178 memcpy((char *)&pp
->refid
, REFID
, 4);
184 * ulink_shutdown - shut down the clock
192 register struct ulinkunit
*up
;
193 struct refclockproc
*pp
;
196 up
= (struct ulinkunit
*)pp
->unitptr
;
197 io_closeclock(&pp
->io
);
203 * ulink_receive - receive data from the serial interface
207 struct recvbuf
*rbufp
210 struct ulinkunit
*up
;
211 struct refclockproc
*pp
;
214 l_fp trtmp
; /* arrival timestamp */
215 int quality
; /* quality indicator */
216 int temp
; /* int temp */
217 char syncchar
; /* synchronization indicator */
218 char leapchar
; /* leap indicator */
219 char modechar
; /* model 320 mode flag */
220 char siglchar
; /* model difference between 33x/325 */
221 char char_quality
[2]; /* temp quality flag */
224 * Initialize pointers and read the timecode and timestamp
226 peer
= (struct peer
*)rbufp
->recv_srcclock
;
228 up
= (struct ulinkunit
*)pp
->unitptr
;
229 temp
= refclock_gtlin(rbufp
, pp
->a_lastcode
, BMAX
, &trtmp
);
232 * Note we get a buffer and timestamp for both a <cr> and <lf>,
233 * but only the <cr> timestamp is retained.
236 if (up
->tcswitch
== 0) {
238 up
->laststamp
= trtmp
;
244 pp
->lastrec
= up
->laststamp
;
245 up
->laststamp
= trtmp
;
249 printf("ulink: timecode %d %s\n", pp
->lencode
,
254 * We get down to business, check the timecode format and decode
255 * its contents. If the timecode has invalid length or is not in
256 * proper format, we declare bad format and exit.
258 syncchar
= leapchar
= modechar
= siglchar
= ' ';
259 switch (pp
->lencode
) {
263 * First we check if the format is 33x or 325:
264 * <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 (33x)
265 * <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5 (325)
266 * simply by comparing if the signal level is 'S' or 'R'
269 if (sscanf(pp
->a_lastcode
, "%c%*31c",
272 if(siglchar
== SIGLCHAR325
) {
275 * decode for a Model 325 decoder.
276 * Timecode format from January 23, 2004 datasheet is:
278 * <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5
280 * R WWVB decodersignal readability R1 - R5
281 * 5 R1 is unreadable, R5 is best
282 * space a space (0x20)
283 * 1 Data bit 0, 1, M (pos mark), or ? (unknown).
284 * C Reception from either (C)olorado or (H)awaii
285 * 00 Hours since last good WWVB frame sync. Will
287 * space Space char (0x20) or (0xa5) if locked to wwvb
288 * YYYY Current year, 2000-2099
289 * + Leap year indicator. '+' if a leap year,
290 * a space (0x20) if not.
291 * DDD Day of year, 000 - 365.
292 * UTC Timezone (always 'UTC').
293 * S Daylight savings indicator
294 * S - standard time (STD) in effect
295 * O - during STD to DST day 0000-2400
296 * D - daylight savings time (DST) in effect
297 * I - during DST to STD day 0000-2400
298 * space Space character (0x20)
300 * : This is the REAL in sync indicator (: = insync)
302 * : : = in sync ? = NOT in sync
304 * L Leap second flag. Changes from space (0x20)
305 * to 'I' or 'D' during month preceding leap
306 * second adjustment. (I)nsert or (D)elete
307 * +5 UT1 correction (sign + digit ))
310 if (sscanf(pp
->a_lastcode
,
311 "%*2c %*2c%2c%*c%4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
312 char_quality
, &pp
->year
, &pp
->day
,
313 &pp
->hour
, &syncchar
, &pp
->minute
, &pp
->second
,
316 if (char_quality
[0] == '0') {
318 } else if (char_quality
[0] == '0') {
319 quality
= (char_quality
[1] & 0x0f);
324 if (leapchar
== 'I' ) leapchar
= '+';
325 if (leapchar
== 'D' ) leapchar
= '-';
330 printf("ulink: char_quality %c %c\n",
331 char_quality[0], char_quality[1]);
332 printf("ulink: quality %d\n", quality);
333 printf("ulink: syncchar %x\n", syncchar);
334 printf("ulink: leapchar %x\n", leapchar);
342 if(siglchar
== SIGLCHAR33x
) {
345 * We got a Model 33X decoder.
346 * Timecode format from January 29, 2001 datasheet is:
347 * <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5
348 * S WWVB decoder sync indicator. S for in-sync(?)
349 * or N for noisy signal.
350 * 9+ RF signal level in S-units, 0-9 followed by
351 * a space (0x20). The space turns to '+' if the
353 * D Data bit 0, 1, 2 (position mark), or
355 * space Space character (0x20)
356 * 00 Hours since last good WWVB frame sync. Will
357 * be 00-23 hrs, or '1d' to '7d'. Will be 'Lk'
358 * if currently in sync.
359 * space Space character (0x20)
360 * YYYY Current year, 1990-2089
361 * + Leap year indicator. '+' if a leap year,
362 * a space (0x20) if not.
363 * DDD Day of year, 001 - 366.
364 * UTC Timezone (always 'UTC').
365 * S Daylight savings indicator
366 * S - standard time (STD) in effect
367 * O - during STD to DST day 0000-2400
368 * D - daylight savings time (DST) in effect
369 * I - during DST to STD day 0000-2400
370 * space Space character (0x20)
372 * : This is the REAL in sync indicator (: = insync)
374 * : : = in sync ? = NOT in sync
376 * L Leap second flag. Changes from space (0x20)
377 * to '+' or '-' during month preceding leap
379 * +5 UT1 correction (sign + digit ))
382 if (sscanf(pp
->a_lastcode
,
383 "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
384 char_quality
, &pp
->year
, &pp
->day
,
385 &pp
->hour
, &syncchar
, &pp
->minute
, &pp
->second
,
388 if (char_quality
[0] == 'L') {
390 } else if (char_quality
[0] == '0') {
391 quality
= (char_quality
[1] & 0x0f);
399 printf("ulink: char_quality %c %c\n",
400 char_quality[0], char_quality[1]);
401 printf("ulink: quality %d\n", quality);
402 printf("ulink: syncchar %x\n", syncchar);
403 printf("ulink: leapchar %x\n", leapchar);
417 * The timecode format is:
419 * <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
423 * S = 'S' -- sync'd in last hour,
424 * '0'-'9' - hours x 10 since last update,
426 * Q = Number of correlating time-frames, from 0 to 5
427 * R = 'R' -- reception in progress,
428 * 'N' -- Noisy reception,
429 * ' ' -- standby mode
430 * YYYY = year from 1990 to 2089
431 * DDD = current day from 1 to 366
432 * + = '+' if current year is a leap year, else ' '
433 * HH = UTC hour 0 to 23
434 * MM = Minutes of current hour from 0 to 59
435 * SS = Seconds of current minute from 0 to 59
436 * mm = 10's milliseconds of the current second from 00 to 99
437 * L = Leap second pending at end of month
438 * 'I' = insert, 'D'= delete
439 * T = DST <-> STD transition indicators
443 if (sscanf(pp
->a_lastcode
, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c",
444 &syncchar
, &quality
, &modechar
, &pp
->year
, &pp
->day
,
445 &pp
->hour
, &pp
->minute
, &pp
->second
,
446 &pp
->nsec
, &leapchar
) == 10) {
447 pp
->nsec
*= 10000000; /* M320 returns 10's of msecs */
448 if (leapchar
== 'I' ) leapchar
= '+';
449 if (leapchar
== 'D' ) leapchar
= '-';
450 if (syncchar
!= '?' ) syncchar
= ':';
456 refclock_report(peer
, CEVNT_BADREPLY
);
461 * Decode quality indicator
462 * For the 325 & 33x series, the lower the number the "better"
463 * the time is. I used the dispersion as the measure of time
464 * quality. The quality indicator in the 320 is the number of
465 * correlating time frames (the more the better)
469 * The spec sheet for the 325 & 33x series states the clock will
470 * maintain +/-0.002 seconds accuracy when locked to WWVB. This
471 * is indicated by 'Lk' in the quality portion of the incoming
472 * string. When not in lock, a drift of +/-0.015 seconds should
474 * With the quality indicator decoding scheme above, the 'Lk'
475 * condition will produce a quality value of 0. If the quality
476 * indicator starts with '0' then the second character is the
477 * number of hours since we were last locked. If the first
478 * character is anything other than 'L' or '0' then we have been
479 * out of lock for more than 9 hours so we assume the worst and
480 * force a quality value that selects the 'default' maximum
481 * dispersion. The dispersion values below are what came with the
482 * driver. They're not unreasonable so they've not been changed.
485 if (pp
->lencode
== LEN33X
) {
500 pp
->disp
=MAXDISPERSE
;
521 pp
->disp
=MAXDISPERSE
;
528 * Decode synchronization, and leap characters. If
529 * unsynchronized, set the leap bits accordingly and exit.
530 * Otherwise, set the leap bits according to the leap character.
534 pp
->leap
= LEAP_NOTINSYNC
;
535 else if (leapchar
== '+')
536 pp
->leap
= LEAP_ADDSECOND
;
537 else if (leapchar
== '-')
538 pp
->leap
= LEAP_DELSECOND
;
540 pp
->leap
= LEAP_NOWARNING
;
543 * Process the new sample in the median filter and determine the
544 * timecode timestamp.
546 if (!refclock_process(pp
)) {
547 refclock_report(peer
, CEVNT_BADTIME
);
553 * ulink_poll - called by the transmit procedure
562 struct refclockproc
*pp
;
567 if (pp
->sloppyclockflag
& CLK_FLAG1
) {
568 if (write(pp
->io
.fd
, &pollchar
, 1) != 1)
569 refclock_report(peer
, CEVNT_FAULT
);
578 if (pp
->coderecv
== pp
->codeproc
) {
579 refclock_report(peer
, CEVNT_TIMEOUT
);
582 pp
->lastref
= pp
->lastrec
;
583 refclock_receive(peer
);
584 record_clock_stats(&peer
->srcadr
, pp
->a_lastcode
);
585 peer
->burst
= NSTAGE
;
590 int refclock_ulink_bs
;
591 #endif /* REFCLOCK */