1 /* $NetBSD: tg.c,v 1.1.1.1 2006/06/11 15:02:31 kardel Exp $ */
4 * tg.c generate WWV or IRIG signals for test
7 * This program can generate audio signals that simulate the WWV/H
8 * broadcast timecode. Alternatively, it can generate the IRIG-B
9 * timecode commonly used to synchronize laboratory equipment. It is
10 * intended to test the WWV/H driver (refclock_wwv.c) and the IRIG
11 * driver (refclock_irig.c) in the NTP driver collection.
13 * Besides testing the drivers themselves, this program can be used to
14 * synchronize remote machines over audio transmission lines or program
15 * feeds. The program reads the time on the local machine and sets the
16 * initial epoch of the signal generator within one millisecond.
17 * Alernatively, the initial epoch can be set to an arbitrary time. This
18 * is useful when searching for bugs and testing for correct response to
19 * a leap second in UTC. Note however, the ultimate accuracy is limited
20 * by the intrinsic frequency error of the codec sample clock, which can
21 # reach well over 100 PPM.
23 * The default is to route generated signals to the line output
24 * jack; the s option on the command line routes these signals to the
25 * internal speaker as well. The v option controls the speaker volume
26 * over the range 0-255. The signal generator by default uses WWV
27 * format; the h option switches to WWVH format and the i option
28 * switches to IRIG-B format.
30 * Once started the program runs continuously. The default initial epoch
31 * for the signal generator is read from the computer system clock when
32 * the program starts. The y option specifies an alternate epoch using a
33 * string yydddhhmmss, where yy is the year of century, ddd the day of
34 * year, hh the hour of day and mm the minute of hour. For instance,
35 * 1946Z on 1 January 2006 is 060011946. The l option lights the leap
36 * warning bit in the WWV/H timecode, so is handy to check for correct
37 * behavior at the next leap second epoch. The remaining options are
38 * specified below under the Parse Options heading. Most of these are
41 * During operation the program displays the WWV/H timecode (9 digits)
42 * or IRIG timecode (20 digits) as each new string is constructed. The
43 * display is followed by the BCD binary bits as transmitted. Note that
44 * the transmissionorder is low-order first as the frame is processed
45 * left to right. For WWV/H The leap warning L precedes the first bit.
46 * For IRIG the on-time marker M precedes the first (units) bit, so its
47 * code is delayed one bit and the next digit (tens) needs only three
50 * The program has been tested with the Sun Blade 1500 running Solaris
51 * 10, but not yet with other machines. It uses no special features and
52 * should be readily portable to other hardware and operating systems.
57 #include <sys/audio.h>
60 #include <sys/types.h>
66 #define SECOND 8000 /* one second of 125-us samples */
67 #define BUFLNG 400 /* buffer size */
68 #define DEVICE "/dev/audio" /* default audio device */
69 #define WWV 0 /* WWV encoder */
70 #define IRIG 1 /* IRIG-B encoder */
71 #define OFF 0 /* zero amplitude */
72 #define LOW 1 /* low amplitude */
73 #define HIGH 2 /* high amplitude */
74 #define DATA0 200 /* WWV/H 0 pulse */
75 #define DATA1 500 /* WWV/H 1 pulse */
76 #define PI 800 /* WWV/H PI pulse */
77 #define M2 2 /* IRIG 0 pulse */
78 #define M5 5 /* IRIG 1 pulse */
79 #define M8 8 /* IRIG PI pulse */
82 * Companded sine table amplitude 3000 units
84 int c3000
[] = {1, 48, 63, 70, 78, 82, 85, 89, 92, 94, /* 0-9 */
85 96, 98, 99, 100, 101, 101, 102, 103, 103, 103, /* 10-19 */
86 103, 103, 103, 103, 102, 101, 101, 100, 99, 98, /* 20-29 */
87 96, 94, 92, 89, 85, 82, 78, 70, 63, 48, /* 30-39 */
88 129, 176, 191, 198, 206, 210, 213, 217, 220, 222, /* 40-49 */
89 224, 226, 227, 228, 229, 229, 230, 231, 231, 231, /* 50-59 */
90 231, 231, 231, 231, 230, 229, 229, 228, 227, 226, /* 60-69 */
91 224, 222, 220, 217, 213, 210, 206, 198, 191, 176}; /* 70-79 */
93 * Companded sine table amplitude 6000 units
95 int c6000
[] = {1, 63, 78, 86, 93, 98, 101, 104, 107, 110, /* 0-9 */
96 112, 113, 115, 116, 117, 117, 118, 118, 119, 119, /* 10-19 */
97 119, 119, 119, 118, 118, 117, 117, 116, 115, 113, /* 20-29 */
98 112, 110, 107, 104, 101, 98, 93, 86, 78, 63, /* 30-39 */
99 129, 191, 206, 214, 221, 226, 229, 232, 235, 238, /* 40-49 */
100 240, 241, 243, 244, 245, 245, 246, 246, 247, 247, /* 50-59 */
101 247, 247, 247, 246, 246, 245, 245, 244, 243, 241, /* 60-69 */
102 240, 238, 235, 232, 229, 226, 221, 214, 206, 191}; /* 70-79 */
105 * Decoder operations at the end of each second are driven by a state
106 * machine. The transition matrix consists of a dispatch table indexed
107 * by second number. Each entry in the table contains a case switch
108 * number and argument.
111 int sw
; /* case switch number */
112 int arg
; /* argument */
116 * Case switch numbers
118 #define DATA 0 /* send data (0, 1, PI) */
119 #define COEF 1 /* send BCD bit */
120 #define DEC 2 /* decrement to next digit */
121 #define MIN 3 /* minute pulse */
122 #define LEAP 4 /* leap warning */
123 #define DUT1 5 /* DUT1 bits */
124 #define DST1 6 /* DST1 bit */
125 #define DST2 7 /* DST2 bit */
128 * WWV/H format (100-Hz, 9 digits, 1 m frame)
130 struct progx progx
[] = {
131 {MIN
, 800}, /* 0 minute sync pulse */
132 {DATA
, DATA0
}, /* 1 */
133 {DST2
, 0}, /* 2 DST2 */
134 {LEAP
, 0}, /* 3 leap warning */
135 {COEF
, 1}, /* 4 1 year units */
139 {DEC
, DATA0
}, /* 8 */
140 {DATA
, PI
}, /* 9 p1 */
141 {COEF
, 1}, /* 10 1 minute units */
142 {COEF
, 2}, /* 11 2 */
143 {COEF
, 4}, /* 12 4 */
144 {COEF
, 8}, /* 13 8 */
145 {DEC
, DATA0
}, /* 14 */
146 {COEF
, 1}, /* 15 10 minute tens */
147 {COEF
, 2}, /* 16 20 */
148 {COEF
, 4}, /* 17 40 */
149 {COEF
, 8}, /* 18 80 (not used) */
150 {DEC
, PI
}, /* 19 p2 */
151 {COEF
, 1}, /* 20 1 hour units */
152 {COEF
, 2}, /* 21 2 */
153 {COEF
, 4}, /* 22 4 */
154 {COEF
, 8}, /* 23 8 */
155 {DEC
, DATA0
}, /* 24 */
156 {COEF
, 1}, /* 25 10 hour tens */
157 {COEF
, 2}, /* 26 20 */
158 {COEF
, 4}, /* 27 40 (not used) */
159 {COEF
, 8}, /* 28 80 (not used) */
160 {DEC
, PI
}, /* 29 p3 */
161 {COEF
, 1}, /* 30 1 day units */
162 {COEF
, 2}, /* 31 2 */
163 {COEF
, 4}, /* 32 4 */
164 {COEF
, 8}, /* 33 8 */
165 {DEC
, DATA0
}, /* 34 not used */
166 {COEF
, 1}, /* 35 10 day tens */
167 {COEF
, 2}, /* 36 20 */
168 {COEF
, 4}, /* 37 40 */
169 {COEF
, 8}, /* 38 80 */
170 {DEC
, PI
}, /* 39 p4 */
171 {COEF
, 1}, /* 40 100 day hundreds */
172 {COEF
, 2}, /* 41 200 */
173 {COEF
, 4}, /* 42 400 (not used) */
174 {COEF
, 8}, /* 43 800 (not used) */
175 {DEC
, DATA0
}, /* 44 */
176 {DATA
, DATA0
}, /* 45 */
177 {DATA
, DATA0
}, /* 46 */
178 {DATA
, DATA0
}, /* 47 */
179 {DATA
, DATA0
}, /* 48 */
180 {DATA
, PI
}, /* 49 p5 */
181 {DUT1
, 8}, /* 50 DUT1 sign */
182 {COEF
, 1}, /* 51 10 year tens */
183 {COEF
, 2}, /* 52 20 */
184 {COEF
, 4}, /* 53 40 */
185 {COEF
, 8}, /* 54 80 */
186 {DST1
, 0}, /* 55 DST1 */
187 {DUT1
, 1}, /* 56 0.1 DUT1 fraction */
188 {DUT1
, 2}, /* 57 0.2 */
189 {DUT1
, 4}, /* 58 0.4 */
190 {DATA
, PI
}, /* 59 p6 */
191 {DATA
, DATA0
}, /* 60 leap */
195 * IRIG format except first frame (1000 Hz, 20 digits, 1 s frame)
197 struct progx progy
[] = {
198 {COEF
, 1}, /* 0 1 units */
202 {DEC
, M2
}, /* 4 im */
203 {COEF
, 1}, /* 5 10 tens */
204 {COEF
, 2}, /* 6 20 */
205 {COEF
, 4}, /* 7 40 */
206 {COEF
, 8}, /* 8 80 */
207 {DEC
, M8
}, /* 9 pi */
211 * IRIG format first frame (1000 Hz, 20 digits, 1 s frame)
213 struct progx progz
[] = {
214 {MIN
, M8
}, /* 0 pi (second) */
215 {COEF
, 1}, /* 1 1 units */
219 {DEC
, M2
}, /* 5 im */
220 {COEF
, 1}, /* 6 10 tens */
221 {COEF
, 2}, /* 7 20 */
222 {COEF
, 4}, /* 8 40 */
223 {DEC
, M8
}, /* 9 pi */
227 * Forward declarations
229 void sec(int); /* send second */
230 void digit(int); /* encode digit */
231 void peep(int, int, int); /* send cycles */
232 void delay(int); /* delay samples */
237 char buffer
[BUFLNG
]; /* output buffer */
238 int bufcnt
= 0; /* buffer counter */
239 int second
= 0; /* seconds counter */
240 int fd
; /* audio codec file descriptor */
241 int tone
= 1000; /* WWV sync frequency */
242 int level
= AUDIO_MAX_GAIN
/ 8; /* output level */
243 int port
= AUDIO_LINE_OUT
; /* output port */
244 int encode
= WWV
; /* encoder select */
245 int leap
= 0; /* leap indicator */
246 int dst
= 0; /* winter/summer time */
247 int dut1
= 0; /* DUT1 correction (sign, magnitude) */
248 int utc
= 0; /* option epoch */
255 int argc
, /* command line options */
256 char **argv
/* poiniter to list of tokens */
259 struct timeval tv
; /* system clock at startup */
260 audio_info_t info
; /* Sun audio structure */
261 struct tm
*tm
= NULL
; /* structure returned by gmtime */
262 char device
[50]; /* audio device */
263 char code
[100]; /* timecode */
264 int rval
, temp
, arg
, sw
, ptr
;
265 int minute
, hour
, day
, year
;
271 strcpy(device
, DEVICE
);
273 while ((temp
= getopt(argc
, argv
, "a:dhilsu:v:y:")) != -1) {
276 case 'a': /* specify audio device (/dev/audio) */
277 strcpy(device
, optarg
);
280 case 'd': /* set DST for summer (WWV/H only) */
284 case 'h': /* select WWVH sync frequency */
288 case 'i': /* select irig format */
292 case 'l': /* set leap warning bit (WWV/H only) */
296 case 's': /* enable speaker */
297 port
|= AUDIO_SPEAKER
;
300 case 'u': /* set DUT1 offset (-7 to +7) */
301 sscanf(optarg
, "%d", &dut1
);
308 case 'v': /* set output level (0-255) */
309 sscanf(optarg
, "%d", &level
);
312 case 'y': /* set initial date and time */
313 sscanf(optarg
, "%2d%3d%2d%2d", &year
, &day
,
319 printf("invalid option %c\n", temp
);
325 * Open audio device and set options
327 fd
= open("/dev/audio", O_WRONLY
);
329 printf("audio open %s\n", strerror(errno
));
332 rval
= ioctl(fd
, AUDIO_GETINFO
, &info
);
334 printf("audio control %s\n", strerror(errno
));
337 info
.play
.port
= port
;
338 info
.play
.gain
= level
;
339 info
.play
.sample_rate
= SECOND
;
340 info
.play
.channels
= 1;
341 info
.play
.precision
= 8;
342 info
.play
.encoding
= AUDIO_ENCODING_ULAW
;
343 printf("port %d gain %d rate %d chan %d prec %d encode %d\n",
344 info
.play
.port
, info
.play
.gain
, info
.play
.sample_rate
,
345 info
.play
.channels
, info
.play
.precision
,
347 ioctl(fd
, AUDIO_SETINFO
, &info
);
350 * Unless specified otherwise, read the system clock and
351 * initialize the time.
354 gettimeofday(&tv
, NULL
);
355 tm
= gmtime(&tv
.tv_sec
);
358 day
= tm
->tm_yday
+ 1;
359 year
= tm
->tm_year
% 100;
363 * Delay the first second so the generator is accurately
364 * aligned with the system clock within one sample (125
367 delay(SECOND
- tv
.tv_usec
* 8 / 1000);
369 memset(code
, 0, sizeof(code
));
373 * For WWV/H and default time, carefully set the signal
374 * generator seconds number to agree with the current time.
377 printf("year %d day %d time %02d:%02d:%02d tone %d\n",
378 year
, day
, hour
, minute
, second
, tone
);
379 sprintf(code
, "%01d%03d%02d%02d%01d", year
/ 10, day
,
380 hour
, minute
, year
% 10);
381 printf("%s\n", code
);
383 for (i
= 0; i
<= second
; i
++) {
384 if (progx
[i
].sw
== DEC
)
390 * For IRIG the signal generator runs every second, so requires
391 * no additional alignment.
394 printf("sbs %x year %d day %d time %02d:%02d:%02d\n",
395 0, year
, day
, hour
, minute
, second
);
400 * Run the signal generator to generate new timecode strings
401 * once per minute for WWV/H and once per second for IRIG.
406 * Crank the state machine to propagate carries to the
407 * year of century. Note that we delayed up to one
408 * second for alignment after reading the time, so this
409 * is the next second.
411 second
= (second
+ 1) % 60;
424 * At year rollover check for leap second.
426 if (day
>= (year
& 0x3 ? 366 : 367)) {
436 sprintf(code
, "%01d%03d%02d%02d%01d",
437 year
/ 10, day
, hour
, minute
, year
%
439 printf("\n%s\n", code
);
443 if (encode
== IRIG
) {
444 sprintf(code
, "%04x%04d%06d%02d%02d%02d", 0,
445 year
, day
, hour
, minute
, second
);
446 printf("%s\n", code
);
451 * Generate data for the second
456 * The IRIG second consists of 20 BCD digits of width-
457 * modulateod pulses at 2, 5 and 8 ms and modulated 50
458 * percent on the 1000-Hz carrier.
461 for (i
= 0; i
< 100; i
++) {
466 sw
= progy
[i
% 10].sw
;
467 arg
= progy
[i
% 10].arg
;
471 case COEF
: /* send BCD bit */
472 if (code
[ptr
] & arg
) {
473 peep(M5
, 1000, HIGH
);
477 peep(M2
, 1000, HIGH
);
483 case DEC
: /* send IM/PI bit */
486 peep(arg
, 1000, HIGH
);
487 peep(10 - arg
, 1000, LOW
);
490 case MIN
: /* send data bit */
491 peep(arg
, 1000, HIGH
);
492 peep(10 - arg
, 1000, LOW
);
503 * The WWV/H second consists of 9 BCD digits of width-
504 * modulateod pulses 200, 500 and 800 ms at 100-Hz.
507 sw
= progx
[second
].sw
;
508 arg
= progx
[second
].arg
;
511 case DATA
: /* send data bit */
515 case COEF
: /* send BCD bit */
516 if (code
[ptr
] & arg
) {
525 case LEAP
: /* send leap bit */
535 case DEC
: /* send data bit */
541 case MIN
: /* send minute sync */
542 peep(arg
, tone
, HIGH
);
543 peep(1000 - arg
, tone
, OFF
);
546 case DUT1
: /* send DUT1 bits */
553 case DST1
: /* send DST1 bit */
562 case DST2
: /* send DST2 bit */
575 * Generate WWV/H 0 or 1 data pulse.
578 int code
/* DATA0, DATA1, PI */
582 * The WWV data pulse begins with 5 ms of 1000 Hz follwed by a
583 * guard time of 25 ms. The data pulse is 170, 570 or 770 ms at
584 * 100 Hz corresponding to 0, 1 or position indicator (PI),
585 * respectively. Note the 100-Hz data pulses are transmitted 6
586 * dB below the 1000-Hz sync pulses. Originally the data pulses
587 * were transmited 10 dB below the sync pulses, but the station
588 * engineers increased that to 6 dB because the Heath GC-1000
589 * WWV/H radio clock worked much better.
591 peep(5, tone
, HIGH
); /* send seconds tick */
593 peep(code
- 30, 100, LOW
); /* send data */
594 peep(1000 - code
, 100, OFF
);
599 * Generate cycles of 100 Hz or any multiple of 100 Hz.
602 int pulse
, /* pulse length (ms) */
603 int freq
, /* frequency (Hz) */
604 int amp
/* amplitude */
607 int increm
; /* phase increment */
610 if (amp
== OFF
|| freq
== 0)
615 for (i
= 0 ; i
< pulse
* 8; i
++) {
619 buffer
[bufcnt
++] = ~c6000
[j
];
623 buffer
[bufcnt
++] = ~c3000
[j
];
627 buffer
[bufcnt
++] = ~0;
629 if (bufcnt
>= BUFLNG
) {
630 write(fd
, buffer
, BUFLNG
);
633 j
= (j
+ increm
) % 80;
639 * Delay for initial phasing
642 int delay
/* delay in samples */
645 int samples
; /* samples remaining */
648 memset(buffer
, 0, BUFLNG
);
649 while (samples
>= BUFLNG
) {
650 write(fd
, buffer
, BUFLNG
);
653 write(fd
, buffer
, samples
);