Annotation of embedaddon/ntp/util/tg.c, revision 1.1.1.1
1.1 misho 1: /*
2: * tg.c generate WWV or IRIG signals for test
3: */
4: /*
5: * This program can generate audio signals that simulate the WWV/H
6: * broadcast timecode. Alternatively, it can generate the IRIG-B
7: * timecode commonly used to synchronize laboratory equipment. It is
8: * intended to test the WWV/H driver (refclock_wwv.c) and the IRIG
9: * driver (refclock_irig.c) in the NTP driver collection.
10: *
11: * Besides testing the drivers themselves, this program can be used to
12: * synchronize remote machines over audio transmission lines or program
13: * feeds. The program reads the time on the local machine and sets the
14: * initial epoch of the signal generator within one millisecond.
15: * Alernatively, the initial epoch can be set to an arbitrary time. This
16: * is useful when searching for bugs and testing for correct response to
17: * a leap second in UTC. Note however, the ultimate accuracy is limited
18: * by the intrinsic frequency error of the codec sample clock, which can
19: # reach well over 100 PPM.
20: *
21: * The default is to route generated signals to the line output
22: * jack; the s option on the command line routes these signals to the
23: * internal speaker as well. The v option controls the speaker volume
24: * over the range 0-255. The signal generator by default uses WWV
25: * format; the h option switches to WWVH format and the i option
26: * switches to IRIG-B format.
27: *
28: * Once started the program runs continuously. The default initial epoch
29: * for the signal generator is read from the computer system clock when
30: * the program starts. The y option specifies an alternate epoch using a
31: * string yydddhhmmss, where yy is the year of century, ddd the day of
32: * year, hh the hour of day and mm the minute of hour. For instance,
33: * 1946Z on 1 January 2006 is 060011946. The l option lights the leap
34: * warning bit in the WWV/H timecode, so is handy to check for correct
35: * behavior at the next leap second epoch. The remaining options are
36: * specified below under the Parse Options heading. Most of these are
37: * for testing.
38: *
39: * During operation the program displays the WWV/H timecode (9 digits)
40: * or IRIG timecode (20 digits) as each new string is constructed. The
41: * display is followed by the BCD binary bits as transmitted. Note that
42: * the transmissionorder is low-order first as the frame is processed
43: * left to right. For WWV/H The leap warning L preceeds the first bit.
44: * For IRIG the on-time marker M preceeds the first (units) bit, so its
45: * code is delayed one bit and the next digit (tens) needs only three
46: * bits.
47: *
48: * The program has been tested with the Sun Blade 1500 running Solaris
49: * 10, but not yet with other machines. It uses no special features and
50: * should be readily portable to other hardware and operating systems.
51: */
52: #include <stdio.h>
53: #include <stdlib.h>
54: #include <time.h>
55: #include <sys/audio.h>
56: #include <math.h>
57: #include <errno.h>
58: #include <sys/types.h>
59: #include <sys/stat.h>
60: #include <fcntl.h>
61: #include <string.h>
62: #include <unistd.h>
63:
64: #define SECOND 8000 /* one second of 125-us samples */
65: #define BUFLNG 400 /* buffer size */
66: #define DEVICE "/dev/audio" /* default audio device */
67: #define WWV 0 /* WWV encoder */
68: #define IRIG 1 /* IRIG-B encoder */
69: #define OFF 0 /* zero amplitude */
70: #define LOW 1 /* low amplitude */
71: #define HIGH 2 /* high amplitude */
72: #define DATA0 200 /* WWV/H 0 pulse */
73: #define DATA1 500 /* WWV/H 1 pulse */
74: #define PI 800 /* WWV/H PI pulse */
75: #define M2 2 /* IRIG 0 pulse */
76: #define M5 5 /* IRIG 1 pulse */
77: #define M8 8 /* IRIG PI pulse */
78:
79: /*
80: * Companded sine table amplitude 3000 units
81: */
82: int c3000[] = {1, 48, 63, 70, 78, 82, 85, 89, 92, 94, /* 0-9 */
83: 96, 98, 99, 100, 101, 101, 102, 103, 103, 103, /* 10-19 */
84: 103, 103, 103, 103, 102, 101, 101, 100, 99, 98, /* 20-29 */
85: 96, 94, 92, 89, 85, 82, 78, 70, 63, 48, /* 30-39 */
86: 129, 176, 191, 198, 206, 210, 213, 217, 220, 222, /* 40-49 */
87: 224, 226, 227, 228, 229, 229, 230, 231, 231, 231, /* 50-59 */
88: 231, 231, 231, 231, 230, 229, 229, 228, 227, 226, /* 60-69 */
89: 224, 222, 220, 217, 213, 210, 206, 198, 191, 176}; /* 70-79 */
90: /*
91: * Companded sine table amplitude 6000 units
92: */
93: int c6000[] = {1, 63, 78, 86, 93, 98, 101, 104, 107, 110, /* 0-9 */
94: 112, 113, 115, 116, 117, 117, 118, 118, 119, 119, /* 10-19 */
95: 119, 119, 119, 118, 118, 117, 117, 116, 115, 113, /* 20-29 */
96: 112, 110, 107, 104, 101, 98, 93, 86, 78, 63, /* 30-39 */
97: 129, 191, 206, 214, 221, 226, 229, 232, 235, 238, /* 40-49 */
98: 240, 241, 243, 244, 245, 245, 246, 246, 247, 247, /* 50-59 */
99: 247, 247, 247, 246, 246, 245, 245, 244, 243, 241, /* 60-69 */
100: 240, 238, 235, 232, 229, 226, 221, 214, 206, 191}; /* 70-79 */
101:
102: /*
103: * Decoder operations at the end of each second are driven by a state
104: * machine. The transition matrix consists of a dispatch table indexed
105: * by second number. Each entry in the table contains a case switch
106: * number and argument.
107: */
108: struct progx {
109: int sw; /* case switch number */
110: int arg; /* argument */
111: };
112:
113: /*
114: * Case switch numbers
115: */
116: #define DATA 0 /* send data (0, 1, PI) */
117: #define COEF 1 /* send BCD bit */
118: #define DEC 2 /* decrement to next digit */
119: #define MIN 3 /* minute pulse */
120: #define LEAP 4 /* leap warning */
121: #define DUT1 5 /* DUT1 bits */
122: #define DST1 6 /* DST1 bit */
123: #define DST2 7 /* DST2 bit */
124:
125: /*
126: * WWV/H format (100-Hz, 9 digits, 1 m frame)
127: */
128: struct progx progx[] = {
129: {MIN, 800}, /* 0 minute sync pulse */
130: {DATA, DATA0}, /* 1 */
131: {DST2, 0}, /* 2 DST2 */
132: {LEAP, 0}, /* 3 leap warning */
133: {COEF, 1}, /* 4 1 year units */
134: {COEF, 2}, /* 5 2 */
135: {COEF, 4}, /* 6 4 */
136: {COEF, 8}, /* 7 8 */
137: {DEC, DATA0}, /* 8 */
138: {DATA, PI}, /* 9 p1 */
139: {COEF, 1}, /* 10 1 minute units */
140: {COEF, 2}, /* 11 2 */
141: {COEF, 4}, /* 12 4 */
142: {COEF, 8}, /* 13 8 */
143: {DEC, DATA0}, /* 14 */
144: {COEF, 1}, /* 15 10 minute tens */
145: {COEF, 2}, /* 16 20 */
146: {COEF, 4}, /* 17 40 */
147: {COEF, 8}, /* 18 80 (not used) */
148: {DEC, PI}, /* 19 p2 */
149: {COEF, 1}, /* 20 1 hour units */
150: {COEF, 2}, /* 21 2 */
151: {COEF, 4}, /* 22 4 */
152: {COEF, 8}, /* 23 8 */
153: {DEC, DATA0}, /* 24 */
154: {COEF, 1}, /* 25 10 hour tens */
155: {COEF, 2}, /* 26 20 */
156: {COEF, 4}, /* 27 40 (not used) */
157: {COEF, 8}, /* 28 80 (not used) */
158: {DEC, PI}, /* 29 p3 */
159: {COEF, 1}, /* 30 1 day units */
160: {COEF, 2}, /* 31 2 */
161: {COEF, 4}, /* 32 4 */
162: {COEF, 8}, /* 33 8 */
163: {DEC, DATA0}, /* 34 not used */
164: {COEF, 1}, /* 35 10 day tens */
165: {COEF, 2}, /* 36 20 */
166: {COEF, 4}, /* 37 40 */
167: {COEF, 8}, /* 38 80 */
168: {DEC, PI}, /* 39 p4 */
169: {COEF, 1}, /* 40 100 day hundreds */
170: {COEF, 2}, /* 41 200 */
171: {COEF, 4}, /* 42 400 (not used) */
172: {COEF, 8}, /* 43 800 (not used) */
173: {DEC, DATA0}, /* 44 */
174: {DATA, DATA0}, /* 45 */
175: {DATA, DATA0}, /* 46 */
176: {DATA, DATA0}, /* 47 */
177: {DATA, DATA0}, /* 48 */
178: {DATA, PI}, /* 49 p5 */
179: {DUT1, 8}, /* 50 DUT1 sign */
180: {COEF, 1}, /* 51 10 year tens */
181: {COEF, 2}, /* 52 20 */
182: {COEF, 4}, /* 53 40 */
183: {COEF, 8}, /* 54 80 */
184: {DST1, 0}, /* 55 DST1 */
185: {DUT1, 1}, /* 56 0.1 DUT1 fraction */
186: {DUT1, 2}, /* 57 0.2 */
187: {DUT1, 4}, /* 58 0.4 */
188: {DATA, PI}, /* 59 p6 */
189: {DATA, DATA0}, /* 60 leap */
190: };
191:
192: /*
193: * IRIG format except first frame (1000 Hz, 20 digits, 1 s frame)
194: */
195: struct progx progy[] = {
196: {COEF, 1}, /* 0 1 units */
197: {COEF, 2}, /* 1 2 */
198: {COEF, 4}, /* 2 4 */
199: {COEF, 8}, /* 3 8 */
200: {DEC, M2}, /* 4 im */
201: {COEF, 1}, /* 5 10 tens */
202: {COEF, 2}, /* 6 20 */
203: {COEF, 4}, /* 7 40 */
204: {COEF, 8}, /* 8 80 */
205: {DEC, M8}, /* 9 pi */
206: };
207:
208: /*
209: * IRIG format first frame (1000 Hz, 20 digits, 1 s frame)
210: */
211: struct progx progz[] = {
212: {MIN, M8}, /* 0 pi (second) */
213: {COEF, 1}, /* 1 1 units */
214: {COEF, 2}, /* 2 2 */
215: {COEF, 4}, /* 3 4 */
216: {COEF, 8}, /* 4 8 */
217: {DEC, M2}, /* 5 im */
218: {COEF, 1}, /* 6 10 tens */
219: {COEF, 2}, /* 7 20 */
220: {COEF, 4}, /* 8 40 */
221: {DEC, M8}, /* 9 pi */
222: };
223:
224: /*
225: * Forward declarations
226: */
227: void sec(int); /* send second */
228: void digit(int); /* encode digit */
229: void peep(int, int, int); /* send cycles */
230: void delay(int); /* delay samples */
231:
232: /*
233: * Global variables
234: */
235: char buffer[BUFLNG]; /* output buffer */
236: int bufcnt = 0; /* buffer counter */
237: int second = 0; /* seconds counter */
238: int fd; /* audio codec file descriptor */
239: int tone = 1000; /* WWV sync frequency */
240: int level = AUDIO_MAX_GAIN / 8; /* output level */
241: int port = AUDIO_LINE_OUT; /* output port */
242: int encode = WWV; /* encoder select */
243: int leap = 0; /* leap indicator */
244: int dst = 0; /* winter/summer time */
245: int dut1 = 0; /* DUT1 correction (sign, magnitude) */
246: int utc = 0; /* option epoch */
247:
248: /*
249: * Main program
250: */
251: int
252: main(
253: int argc, /* command line options */
254: char **argv /* poiniter to list of tokens */
255: )
256: {
257: struct timeval tv; /* system clock at startup */
258: audio_info_t info; /* Sun audio structure */
259: struct tm *tm = NULL; /* structure returned by gmtime */
260: char device[50]; /* audio device */
261: char code[100]; /* timecode */
262: int rval, temp, arg, sw, ptr;
263: int minute, hour, day, year;
264: int i;
265:
266: /*
267: * Parse options
268: */
269: strcpy(device, DEVICE);
270: year = 0;
271: while ((temp = getopt(argc, argv, "a:dhilsu:v:y:")) != -1) {
272: switch (temp) {
273:
274: case 'a': /* specify audio device (/dev/audio) */
275: strcpy(device, optarg);
276: break;
277:
278: case 'd': /* set DST for summer (WWV/H only) */
279: dst++;
280: break;
281:
282: case 'h': /* select WWVH sync frequency */
283: tone = 1200;
284: break;
285:
286: case 'i': /* select irig format */
287: encode = IRIG;
288: break;
289:
290: case 'l': /* set leap warning bit (WWV/H only) */
291: leap++;
292: break;
293:
294: case 's': /* enable speaker */
295: port |= AUDIO_SPEAKER;
296: break;
297:
298: case 'u': /* set DUT1 offset (-7 to +7) */
299: sscanf(optarg, "%d", &dut1);
300: if (dut1 < 0)
301: dut1 = abs(dut1);
302: else
303: dut1 |= 0x8;
304: break;
305:
306: case 'v': /* set output level (0-255) */
307: sscanf(optarg, "%d", &level);
308: break;
309:
310: case 'y': /* set initial date and time */
311: sscanf(optarg, "%2d%3d%2d%2d", &year, &day,
312: &hour, &minute);
313: utc++;
314: break;
315:
316: defult:
317: printf("invalid option %c\n", temp);
318: break;
319: }
320: }
321:
322: /*
323: * Open audio device and set options
324: */
325: fd = open("/dev/audio", O_WRONLY);
326: if (fd <= 0) {
327: printf("audio open %s\n", strerror(errno));
328: exit(1);
329: }
330: rval = ioctl(fd, AUDIO_GETINFO, &info);
331: if (rval < 0) {
332: printf("audio control %s\n", strerror(errno));
333: exit(0);
334: }
335: info.play.port = port;
336: info.play.gain = level;
337: info.play.sample_rate = SECOND;
338: info.play.channels = 1;
339: info.play.precision = 8;
340: info.play.encoding = AUDIO_ENCODING_ULAW;
341: printf("port %d gain %d rate %d chan %d prec %d encode %d\n",
342: info.play.port, info.play.gain, info.play.sample_rate,
343: info.play.channels, info.play.precision,
344: info.play.encoding);
345: ioctl(fd, AUDIO_SETINFO, &info);
346:
347: /*
348: * Unless specified otherwise, read the system clock and
349: * initialize the time.
350: */
351: if (!utc) {
352: gettimeofday(&tv, NULL);
353: tm = gmtime(&tv.tv_sec);
354: minute = tm->tm_min;
355: hour = tm->tm_hour;
356: day = tm->tm_yday + 1;
357: year = tm->tm_year % 100;
358: second = tm->tm_sec;
359:
360: /*
361: * Delay the first second so the generator is accurately
362: * aligned with the system clock within one sample (125
363: * microseconds ).
364: */
365: delay(SECOND - tv.tv_usec * 8 / 1000);
366: }
367: memset(code, 0, sizeof(code));
368: switch (encode) {
369:
370: /*
371: * For WWV/H and default time, carefully set the signal
372: * generator seconds number to agree with the current time.
373: */
374: case WWV:
375: printf("year %d day %d time %02d:%02d:%02d tone %d\n",
376: year, day, hour, minute, second, tone);
377: sprintf(code, "%01d%03d%02d%02d%01d", year / 10, day,
378: hour, minute, year % 10);
379: printf("%s\n", code);
380: ptr = 8;
381: for (i = 0; i <= second; i++) {
382: if (progx[i].sw == DEC)
383: ptr--;
384: }
385: break;
386:
387: /*
388: * For IRIG the signal generator runs every second, so requires
389: * no additional alignment.
390: */
391: case IRIG:
392: printf("sbs %x year %d day %d time %02d:%02d:%02d\n",
393: 0, year, day, hour, minute, second);
394: break;
395: }
396:
397: /*
398: * Run the signal generator to generate new timecode strings
399: * once per minute for WWV/H and once per second for IRIG.
400: */
401: while(1) {
402:
403: /*
404: * Crank the state machine to propagate carries to the
405: * year of century. Note that we delayed up to one
406: * second for alignment after reading the time, so this
407: * is the next second.
408: */
409: second = (second + 1) % 60;
410: if (second == 0) {
411: minute++;
412: if (minute >= 60) {
413: minute = 0;
414: hour++;
415: }
416: if (hour >= 24) {
417: hour = 0;
418: day++;
419: }
420:
421: /*
422: * At year rollover check for leap second.
423: */
424: if (day >= (year & 0x3 ? 366 : 367)) {
425: if (leap) {
426: sec(DATA0);
427: printf("\nleap!");
428: leap = 0;
429: }
430: day = 1;
431: year++;
432: }
433: if (encode == WWV) {
434: sprintf(code, "%01d%03d%02d%02d%01d",
435: year / 10, day, hour, minute, year %
436: 10);
437: printf("\n%s\n", code);
438: ptr = 8;
439: }
440: }
441: if (encode == IRIG) {
442: sprintf(code, "%04x%04d%06d%02d%02d%02d", 0,
443: year, day, hour, minute, second);
444: printf("%s\n", code);
445: ptr = 19;
446: }
447:
448: /*
449: * Generate data for the second
450: */
451: switch(encode) {
452:
453: /*
454: * The IRIG second consists of 20 BCD digits of width-
455: * modulateod pulses at 2, 5 and 8 ms and modulated 50
456: * percent on the 1000-Hz carrier.
457: */
458: case IRIG:
459: for (i = 0; i < 100; i++) {
460: if (i < 10) {
461: sw = progz[i].sw;
462: arg = progz[i].arg;
463: } else {
464: sw = progy[i % 10].sw;
465: arg = progy[i % 10].arg;
466: }
467: switch(sw) {
468:
469: case COEF: /* send BCD bit */
470: if (code[ptr] & arg) {
471: peep(M5, 1000, HIGH);
472: peep(M5, 1000, LOW);
473: printf("1");
474: } else {
475: peep(M2, 1000, HIGH);
476: peep(M8, 1000, LOW);
477: printf("0");
478: }
479: break;
480:
481: case DEC: /* send IM/PI bit */
482: ptr--;
483: printf(" ");
484: peep(arg, 1000, HIGH);
485: peep(10 - arg, 1000, LOW);
486: break;
487:
488: case MIN: /* send data bit */
489: peep(arg, 1000, HIGH);
490: peep(10 - arg, 1000, LOW);
491: printf("M ");
492: break;
493: }
494: if (ptr < 0)
495: break;
496: }
497: printf("\n");
498: break;
499:
500: /*
501: * The WWV/H second consists of 9 BCD digits of width-
502: * modulateod pulses 200, 500 and 800 ms at 100-Hz.
503: */
504: case WWV:
505: sw = progx[second].sw;
506: arg = progx[second].arg;
507: switch(sw) {
508:
509: case DATA: /* send data bit */
510: sec(arg);
511: break;
512:
513: case COEF: /* send BCD bit */
514: if (code[ptr] & arg) {
515: sec(DATA1);
516: printf("1");
517: } else {
518: sec(DATA0);
519: printf("0");
520: }
521: break;
522:
523: case LEAP: /* send leap bit */
524: if (leap) {
525: sec(DATA1);
526: printf("L ");
527: } else {
528: sec(DATA0);
529: printf(" ");
530: }
531: break;
532:
533: case DEC: /* send data bit */
534: ptr--;
535: sec(arg);
536: printf(" ");
537: break;
538:
539: case MIN: /* send minute sync */
540: peep(arg, tone, HIGH);
541: peep(1000 - arg, tone, OFF);
542: break;
543:
544: case DUT1: /* send DUT1 bits */
545: if (dut1 & arg)
546: sec(DATA1);
547: else
548: sec(DATA0);
549: break;
550:
551: case DST1: /* send DST1 bit */
552: ptr--;
553: if (dst)
554: sec(DATA1);
555: else
556: sec(DATA0);
557: printf(" ");
558: break;
559:
560: case DST2: /* send DST2 bit */
561: if (dst)
562: sec(DATA1);
563: else
564: sec(DATA0);
565: break;
566: }
567: }
568: }
569: }
570:
571:
572: /*
573: * Generate WWV/H 0 or 1 data pulse.
574: */
575: void sec(
576: int code /* DATA0, DATA1, PI */
577: )
578: {
579: /*
580: * The WWV data pulse begins with 5 ms of 1000 Hz follwed by a
581: * guard time of 25 ms. The data pulse is 170, 570 or 770 ms at
582: * 100 Hz corresponding to 0, 1 or position indicator (PI),
583: * respectively. Note the 100-Hz data pulses are transmitted 6
584: * dB below the 1000-Hz sync pulses. Originally the data pulses
585: * were transmited 10 dB below the sync pulses, but the station
586: * engineers increased that to 6 dB because the Heath GC-1000
587: * WWV/H radio clock worked much better.
588: */
589: peep(5, tone, HIGH); /* send seconds tick */
590: peep(25, tone, OFF);
591: peep(code - 30, 100, LOW); /* send data */
592: peep(1000 - code, 100, OFF);
593: }
594:
595:
596: /*
597: * Generate cycles of 100 Hz or any multiple of 100 Hz.
598: */
599: void peep(
600: int pulse, /* pulse length (ms) */
601: int freq, /* frequency (Hz) */
602: int amp /* amplitude */
603: )
604: {
605: int increm; /* phase increment */
606: int i, j;
607:
608: if (amp == OFF || freq == 0)
609: increm = 10;
610: else
611: increm = freq / 100;
612: j = 0;
613: for (i = 0 ; i < pulse * 8; i++) {
614: switch (amp) {
615:
616: case HIGH:
617: buffer[bufcnt++] = ~c6000[j];
618: break;
619:
620: case LOW:
621: buffer[bufcnt++] = ~c3000[j];
622: break;
623:
624: default:
625: buffer[bufcnt++] = ~0;
626: }
627: if (bufcnt >= BUFLNG) {
628: write(fd, buffer, BUFLNG);
629: bufcnt = 0;
630: }
631: j = (j + increm) % 80;
632: }
633: }
634:
635:
636: /*
637: * Delay for initial phasing
638: */
639: void delay (
640: int delay /* delay in samples */
641: )
642: {
643: int samples; /* samples remaining */
644:
645: samples = delay;
646: memset(buffer, 0, BUFLNG);
647: while (samples >= BUFLNG) {
648: write(fd, buffer, BUFLNG);
649: samples -= BUFLNG;
650: }
651: write(fd, buffer, samples);
652: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>