Date: Thursday, 16 June 1988 16:55-MDT From: attcan!utzoo!dciem!nrcaer!cognos!bruce at uunet.uu.net (Dwayne Bruce) Re: N.B.S. Time Service Well kids, after reading about the NBS telephone time service, and having read about an expensive commerical system from Precision Standard Time, Inc., I just had to tell you about the system I cooked up. We've been keeping our VAXen in sync with a simple system that uses the AFSK time code produced by CHU. CHU is Canada's provider of brodcast time, and it has a signal that can be heard regularly all over the western hemisphere. During seconds 31..39 of each minute, they transmit a time code in 103-type AFSK. I built a modem, plugged it into an HF receiver, and wrote a piece of code to handle the output. The code is enclosed. If you want to know more about the hardware, consult the article I wrote in the April, 1988 edition of "The Canadian Amateur" (published by the Canadian Amateur Radio Federation). Anyway, here's the code. It runs under UNIX and VMS. ------------------------- /* CHU Marcus Leech VE3MDL Aug 1987 */ /* This program reads and understands the timecode format * produced by the CHU broadcast signal. This signal provides * an atomic time standard to most places in North and South * America on 3.330 7.335 and 14.670 MHZ, mode A3. During * seconds 31 thru 39 the time is broadcast using Bell 103 AFSK. * The time code looks like this: * 6.d|d.d|h.h|m.m|s.s|6.d|d.d|h.h|m.m|s.s * The time code is repeated twice for error detection. Each . represents * one nibble boundary. The nibbles get transmitted reversed, so you * have to flip them to make sense of the time code. Each nibble is a BCD * digit. * It takes one argument, the input device, and some optional flags: * chu [-adst] * - * a adjust attempt to adjust the system clock to CHU time * d daemon run continuously as a daemon * s show show semi-raw timecode * t tune show chars that wouldn't get considered for time code * * When used as a system-time-adjuster, you need to set the system time * to within 2mins of the correct time. The program will drag the * clock into exact synchronism. */ #include #ifndef VMS #include #include #include #else VMS #include #include #endif VMS /* Macros to fetch individual nibbles. */ #define hinib(x) (((x) >> 4) & 0x0F) #define lonib(x) ((x) & 0x0F) /* Macro to restore correct ordering within a byte. */ #define byte(h,l) (((l) << 4)|(h)) #define ADJINT 27 #define TWEAK 50 + 12 /* Fudge factor in centiseconds. */ /* CHU code is skewed by 0.5 seconds. */ #define iabs(x) ((x < 0) ? -x : x) char sample1[5]; /* The first time code. */ char sample2[5]; /* The second (error checking) time code. */ main (argc, argv) int argc; char **argv; { char c, *p; #ifndef VMS struct sgttyb sgb; /* For fiddling with tty line params. */ #endif VMS int line; /* Fd for the tty line.*/ int i; int adjcnt; /* Number of samples before we adjust.*/ struct tm *localtime(), *ltp, *gmtime(); /* To break out the time. */ time_t now, time(); #ifdef VMS long t0[2], t1[2], t2[2]; #endif VMS int amm, ass, mmss, diff; char *devstr; int adjust; int show; int tune; int daemon; setbuf (stdout, NULL); adjust = show = tune = 0; devstr = "/dev/null"; for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { p = &argv[i][1]; while (*p) { switch (*p) { case 'a': adjust++; break; case 't': tune++; break; case 's': show++; break; case 'd': daemon++; break; default: fprintf (stdout, "unknown flag '%c'\n", *p); exit (1); } *p++; } } else { strcpy (devstr, argv[i]); } } line = open (devstr, 0); #ifndef VMS /* Set up 8-bit datapath, at 30CPS. */ gtty (line, &sgb); sgb.sg_ispeed = sgb.sg_ospeed = B300; sgb.sg_flags |= RAW; stty (line, &sgb); #endif VMS adjcnt = 0; if (!daemon) { adjcnt = 19; } /* Read forever, waiting for the synchronizing BCD 6 digit to appear. */ for (;;) { read (line, &c, 1); /* We have a syncronizing digit. Grab two samples and compare. */ if (lonib(c) == 6) { /* Get first sample. */ sample1[0] = byte(hinib(c),lonib(c)); for (i = 1; i < 5; i++) { read (line, &c, 1); sample1[i] = byte(hinib(c),lonib(c)); } /* Get second sample. */ for (i = 0; i < 5; i++) { read (line, &c, 1); sample2[i] = byte(hinib(c),lonib(c)); } /* If samples match, we have a valid time code. */ if (compare (sample1, sample2, 5) == 0) { /* Show the code (if -s). The high-order nibble in the * first byte is the synch digit, so it gets masked out * for printing. */ if (show) { fprintf (stdout, "TC: "); for (i = 0; i < 5; i++) { fprintf (stdout, "%02x", sample1[i]); } fprintf (stdout, "\n"); } if (adjcnt++ >= ADJINT) { adjcnt = 0; /* Fetch UTC (GMT). */ time (&now); ltp = gmtime (&now); /* Convert time code minutes and seconds into * binary. */ amm = (hinib(sample1[3]) * 10) + lonib(sample1[3]); ass = (hinib(sample1[4]) * 10) + lonib(sample1[4]); /* Convert minutes and seconds portion of system time. */ mmss = (ltp->tm_min * 60) + ltp->tm_sec; /* Compute the difference. */ diff = ((amm * 60) + ass) - mmss; /* Adjust the system time. */ now += (long)diff; if (iabs(diff) > 120) { fprintf (stdout, "%02d-%02d-%02d %02d:%02d:%02d ", ltp->tm_year, ltp->tm_mon + 1, ltp->tm_mday, ltp->tm_hour, ltp->tm_min, ltp->tm_sec); fprintf (stdout, "TERROR %d\n", diff); if (!daemon) { break; } continue; } /* Only do it if there IS a (reasonable) difference. */ if ((diff != 0) && (adjust)) { ltp = localtime (&now); fprintf (stdout, "%02d-%02d-%02d %02d:%02d:%02d ", ltp->tm_year, ltp->tm_mon + 1, ltp->tm_mday, ltp->tm_hour, ltp->tm_min, ltp->tm_sec); fprintf (stdout, "ADJUST %d\n", diff); /* What we'd REALLY like here is a system call of * the form: stime (*time, ticks) * that would allow you to set the system tick * counter to in centiseconds. Too bad. *-->stime (&now, TWEAK);<-- */ #ifndef VMS #ifndef EUNICE stime (&now); #endif EUNICE #else VMS t1[0] = 100000 * ((diff * 100) + TWEAK); t1[1] = 0; if (diff < 0) { t1[1] = -1; } sys$gettim (t0); lib$addx (t0, t1, t2); sys$setime (t2); #endif VMS } ltp = localtime (&now); fprintf (stdout, "%02d-%02d-%02d %02d:%02d:%02d TVALID\n", ltp->tm_year, ltp->tm_mon + 1, ltp->tm_mday, ltp->tm_hour, ltp->tm_min, ltp->tm_sec); if (!daemon) { break; } } } } else if (tune) { fprintf (stdout, "FT: %c (%02x)\n", c, (unsigned)c); } } } /* Compare two byte-arrays (s1, s2) of length cnt. */ compare (s1, s2, cnt) char *s1; char *s2; int cnt; { int i; for (i = 0; i < cnt; i++) { if (*s1++ != *s2++) { return (1); } } return (0); } #ifdef VMS struct tm * gmtime (tod) time_t *tod; { int hh; int mm; char s; int secdiff, sgn; time_t utc; char *p, *getenv (); p = getenv ("UTCDISP"); sscanf (p, "%c %02d:%02d", &s, &hh, &mm); utc = *tod; secdiff = (hh * 3600) + (mm * 60); sgn = (s == '+') ? 1 : -1; secdiff *= sgn; utc += secdiff; return (localtime (&utc)); } #endif VMS