// // pcfc -- PC Frequency Counter using serial port. // // Works well for low frequencies (less than 100 Hz, roughly). // // Proof of concept program to measure 60 Hz frequency. // - Get a low voltage (5 to 12 Vrms) AC (not DC) transformer. // - Connect one wire to serial port pin 5 (Gnd). // - Connect the other wire to serial port pin 1 (DCD). // - Run this program with the COM port number as argument. // // 22-Sep-2011 Tom Van Baak (tvb) www.LeapSecond.com/tools // #include #include void com_open (int port, int baud); void com_close (void); void com_wait_DCD_rising (void); double microclock (void); void main (int argc, char *argv[]) { int com_port; double expected_hz, seconds_start, seconds_now, elapsed_time, elapsed_prev; long total_cycles, next_display_time, cycles_prev, gate_time, max_time; printf("PCFC -- PC-based Frequency Counter\n"); if (argc < 2) { fprintf(stderr, "Usage: %s [COM port] [Hz expected] [gate time] [max time]\n", argv[0]); fprintf(stderr, "Example: %s 3\n", argv[0]); fprintf(stderr, "Example: %s 10 60\n", argv[0]); fprintf(stderr, "Example: %s 1 32.768 10\n", argv[0]); fprintf(stderr, "Example: %s 1 32.768 30 3600\n", argv[0]); exit(1); } com_port = (argc > 1) ? atoi(argv[1]) : 1; expected_hz = (argc > 2) ? atof(argv[2]) : 0.0; gate_time = (argc > 3) ? atol(argv[3]) : 1; max_time = (argc > 4) ? atol(argv[4]) : 0; com_open(com_port, 9600); printf("Waiting for rising edge of DCD (pin1)\n"); com_wait_DCD_rising(); seconds_start = microclock(); // microsecond resolution but not necessarily accurate printf("Reporting (running average) frequency every %ld seconds\n", gate_time); total_cycles = cycles_prev = 0; next_display_time = gate_time; while (1) { com_wait_DCD_rising(); seconds_now = microclock(); total_cycles += 1; elapsed_time = seconds_now - seconds_start; // Display results once a second. if (elapsed_time >= next_display_time) { // Display total cycles, total seconds and running average Hz. printf("%9ld cycles, %12.6lf seconds, %12.6lf Hz", total_cycles, elapsed_time, total_cycles / elapsed_time); // Display incremental results and relative frequency error. if (expected_hz != 0.0 && cycles_prev != 0) { long de = total_cycles - cycles_prev; double dt = elapsed_time - elapsed_prev; double df = de / dt; printf(" %3ld %8.6lf %10.6lf", de, dt, df); printf(" %14.6le", (df - expected_hz) / expected_hz); } printf("\n"); cycles_prev = total_cycles; elapsed_prev = elapsed_time; next_display_time += gate_time; } if (max_time != 0 && elapsed_time >= max_time) { break; } } com_close(); } // ------------------------------------------------------------------------ // // // Win32 specific code for modem signals. // #define WIN32_LEAN_AND_MEAN #include // Return microsecond resolution elapsed time (units are seconds). // - based on ISA clock, which is precise but not necessarily accurate. double microclock (void) { static LARGE_INTEGER base, freq = { 0, 0 }; // STATIC LARGE_INTEGER now; QueryPerformanceCounter(&now); if (freq.LowPart == 0) { QueryPerformanceFrequency(&freq); base = now; } return (double)(now.QuadPart - base.QuadPart) / (double)freq.LowPart; } #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 com_open (int port, int baud) { char CommPath[100]; DCB Dcb; BOOL Success; sprintf(CommPath, "\\\\.\\COM%d", port); 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); } memset(&Dcb, 0x0, sizeof(DCB)); Dcb.DCBlength = sizeof (DCB); Success = GetCommState(hCom, &Dcb); ASSERT_SUCCESS(GetCommState); Dcb.Parity = NOPARITY; Dcb.BaudRate = baud; Dcb.ByteSize = 8; Dcb.StopBits = ONESTOPBIT; Success = SetCommState(hCom, &Dcb); ASSERT_SUCCESS(SetCommState); printf("Opened %s\n", CommPath); } void com_close (void) { CloseHandle(hCom); } // Wait for rising edge of pin1/DCD. void com_wait_DCD_rising (void) { DWORD Event, Modem; BOOL Success; do { Event = EV_RLSD; Success = SetCommMask(hCom, Event); ASSERT_SUCCESS(SetCommMask); Success = WaitCommEvent(hCom, &Event, NULL); ASSERT_SUCCESS(WaitCommEvent); Success = GetCommModemStatus(hCom, &Modem); ASSERT_SUCCESS(GetCommModemStatus); } while ( !(Modem & MS_RLSD_ON) ) ; } // // RS-232 DE9P (9-pin male plug). // COM1 port on rear of PC (or USB/serial adapter) looks like: // // 1 2 3 4 5 // +-----------------------+ // \ . . . . . / // \ / // \ . . . . / // +---------------+ // 6 7 8 9 // // Signal assignments (PC is DTE): // // pin 1 - DCD (PC input) // pin 2 - Rx (PC input) // pin 3 - Tx (PC output) // pin 4 - DTR (PC output) // pin 5 - ground // pin 6 - DSR (PC input) // pin 7 - RTS (PC output) // pin 8 - CTS (PC input) // pin 9 - RI (PC input) // // RS-232 DE9S (9-pin female socket). // as seen on a serial device or cable connected to a PC: // // 5 4 3 2 1 // +-----------------------+ // \ o o o o o / // \ / // \ o o o o / // +---------------+ // 9 8 7 6 //