// // tco2.exe - Convert WWVB time code text into a usuable TCO signal. // // - The timecode is read from standard input. The format is the // typical 60 character string of WWVB 0's, 1's, and 2's. Comment // lines or non-subcode fields are ignored. // // - Output is DTR pin, DB9P pin 4, and ground is DB9P pin 5. // // - Use a series diode to keep TCO signal levels positive. // - Use a resistor divider to achieve proper voltage levels. // - Double check signal with a 'scope before applying to TCO input. // // - Use this tool if tco1.exe doesn't work for your PC. // // - This program should be run as standalone as possible on your // PC because it achieves millisecond timing using CPU busy loops // instead of using some sort of millisleeps. // // - Sample usage for COM1: wwvb1 | tco2 1 // // 08-Feb-2005 /tvb // #include #include #include void CommOpen (int Port, int Baud, int Parity, int Flow); void CommClose(void); void subcode (char *bits, int size); void trim (char *s); int time_milliseconds (void); void DTR_high (void); void DTR_low (void); int main (int argc, char *argv[]) { int length; char line[100], *p, *field; int port; if (argc == 1) { fprintf(stderr, "Usage: tco2 [port] < input\n" "\n" "Options:\n" " - port is COM port number (1)\n" "\n" "Examples:\n" " tco2 1 < wwvb.txt\n" " wwvb1 | tco2 1\n" "\n" "Note:\n" " - Pick any unused serial port. The time code output is pin 4.\n" ); return 1; } port = (argc > 1) ? atoi(argv[1]) : 1; CommOpen(port, 9600, 0, 0); fprintf(stderr, "** Writing subcode signal to COM DB9P pin 4 (DTR)\n"); fprintf(stderr, "** Reading subcode data from stdin...\n"); // Look for about 60 zeros, ones, and twos somewhere in the line while (fgets(line, sizeof line, stdin) != NULL) { trim(line); p = line; while (*p) { while (*p && isspace(*p)) { p++; // Skip leading white space } if (*p) { field = p; length = 0; } while (*p && !isspace(*p)) { p++; // Skip until next white space length++; } if (length >= 59 && length <= 61) { subcode(field, length); break; } } } CommClose(); return 0; } int iswhite (char c) { return (c == '\r') || (c == '\n') || (c == ' ') || (c == '\t'); } void trim (char *s) { while (*s && iswhite(s[strlen(s) - 1])) { s[strlen(s) - 1] = '\0'; } } // The WWVB 1 Hz subcode consists of 0.2 0.5 and 0.8 second wide pulses. #define WWVB_CHAR(c) ( (c) == '0' || (c) == '1' || (c) == '2' ) void subcode (char *bits, int size) { int i; unsigned char c; int ms0, ms1, ms2; for (i = 0; i < size; i += 1) { if ( ! WWVB_CHAR(bits[i]) ) { printf("skip: %s\n", bits); return; } } // Spin until latter half of current second do { ms0 = time_milliseconds(); } while (ms0 < 500); for (i = 0; i < size; i += 1) { DTR_low(); // Spin until seconds rollover do { ms1 = time_milliseconds(); } while (ms1 >= ms0) ; // Set TCO high at top of the second DTR_high(); c = bits[i]; putchar(c); ms2 = 200 + (bits[i] - '0') * 300; // Spin until end of pulse width do { ms0 = time_milliseconds(); } while (ms0 < ms2) ; } printf("\n"); } // Begin MS Windows specific code #define WIN32_LEAN_AND_MEAN #include // return 0 to 999, the millisecond fraction of current PC time. int time_milliseconds (void) { static LARGE_INTEGER base = {0, 0}; static LARGE_INTEGER freq; LARGE_INTEGER t; double d; int ms; QueryPerformanceCounter(&t); if (base.QuadPart == 0) { if ( ! QueryPerformanceFrequency(&freq) ) { return 0; } base = t; } d = (double)(t.QuadPart - base.QuadPart) / (double)freq.QuadPart; ms = (int)(d * 1000); return ms % 1000; } // Begin Comm library code #define ASSERT_SUCCESS(Function) \ if (!Success) { \ fprintf(stderr, "File %s Line %d Function %s failed (%.8x)\n", \ __FILE__, __LINE__, #Function, GetLastError()); \ exit(1); \ } HANDLE hCom; void CommOpen (int Port, int Baud, int Parity, int Flow) { char CommPath[100]; DCB Dcb; BOOL Success; // // Open COM port. // sprintf(CommPath, "\\\\.\\COM%d", Port); fprintf(stderr, "** Opening %s (%d baud)\n", CommPath, Baud); hCom = CreateFile(CommPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hCom == INVALID_HANDLE_VALUE) { fprintf(stderr, "CreateFile failed: %s (%.8x)\n", CommPath, GetLastError()); exit(1); } // // Set comm parameters. // memset(&Dcb, 0x0, sizeof(DCB)); Dcb.DCBlength = sizeof (DCB); Success = GetCommState(hCom, &Dcb); ASSERT_SUCCESS(GetCommState); // Set parity Dcb.Parity = Parity; if (Parity == 0) { Dcb.Parity = NOPARITY; } else if (Parity & 1) { Dcb.Parity = ODDPARITY; } else { Dcb.Parity = EVENPARITY; } // Set frame size Dcb.BaudRate = Baud; Dcb.ByteSize = (Dcb.Parity == NOPARITY) ? 8 : 7; Dcb.StopBits = ONESTOPBIT; // Set raw mode Dcb.fDsrSensitivity = FALSE; Dcb.fOutxCtsFlow = FALSE; Dcb.fOutX = FALSE; Dcb.fInX = FALSE; Dcb.fNull = FALSE; Dcb.fBinary = TRUE; // PC write flow control Dcb.fOutxCtsFlow = FALSE; Dcb.fOutxDsrFlow = FALSE; // PC read flow control Dcb.fDtrControl = Flow ? DTR_CONTROL_HANDSHAKE : DTR_CONTROL_ENABLE; Dcb.fRtsControl = Flow ? RTS_CONTROL_HANDSHAKE : RTS_CONTROL_ENABLE; Success = SetCommState(hCom, &Dcb); ASSERT_SUCCESS(SetCommState); return; } void CommClose (void) { CloseHandle(hCom); hCom = NULL; } // Asserting DTR makes the pin positive (+12 v nominal) void DTR_high (void) { BOOL Success; Success = EscapeCommFunction(hCom, SETDTR); ASSERT_SUCCESS(EscapeCommFunction); return; } // Deasserting DTR makes the pin negative (-12 v nominal) void DTR_low (void) { BOOL Success; Success = EscapeCommFunction(hCom, CLRDTR); ASSERT_SUCCESS(EscapeCommFunction); return; }