/* Program for reading the data from the cheap LCR meter TECPEL LCR 612 (ebay 2005: around 110 EUR). This is a simple program which only reads the data from the meter; remote controll, set etc. is not implemented yet because i don't need it. Compile e. g. with gcc -O3 -lm -pthread -o lcr612 lcr612.c Licence: GPL (GNU PUBLIC LICENCE). rolf.freitag at email.de 2005-11-08, 2017, */ #include // errno, #include // file descriptor, #include // for and, bitand, or, bitor ..., for less errors! #include // common math #include // posix threads #include // signals #include // standard integers #include // standard i/o #include // standard lib, e. g. strtol, exit, #include // strndup, strstr, ... #include // ioctls #include // POSIX Standard: 5.6 File Characteristics, e. g. chmod() #include // time, date, #include // POSIX Standard: 7.1-2 General Terminal Interface, e. g. cfsetospeed() #include // necessary for unlink, gtopt, link, fcmp, getuid, ... // the software date as simple version number #define SOFTWARE_VERSION_NUMBER "2017-01-18" // Dividing of (unsigned) integer numbers with good rounding (with minimized quantisation error). // Integer division (of positive numbers) ignores everything behind the point while good rounding // rounds up >= .5 and rounds down < .5 (kaufmaennische Rundung). #define mc_POS_DIV(a, b) ( (a)/(b) + ( ( (a) % (b) >= (b)/2 ) ? 1 : 0 ) ) static int fd; // file descriptor static long long int g_t; // (global) start time // time with microseconds inline static long long int get_time () { static struct timeval ti; static struct timezone tzp; gettimeofday (&ti, &tzp); return (ti.tv_usec + 1000000 * ((long long int) ti.tv_sec)); } // get_time int open_serial (const char *device, int baudrate) { int fd = 0; speed_t bdflag = 0; static struct termios tty; int modelines = 0; fprintf (stderr, "Using device %s at baudrate %i\n", device, baudrate); switch (baudrate) { case 0: bdflag = B0; // is used to terminate the connection break; case 50: bdflag = B50; break; case 75: bdflag = B75; break; case 110: bdflag = B110; break; case 134: bdflag = B134; break; case 150: bdflag = B150; break; case 200: bdflag = B200; break; case 300: bdflag = B300; break; case 600: bdflag = B600; break; case 1200: bdflag = B1200; break; case 1800: bdflag = B1800; break; case 2400: bdflag = B2400; break; case 4800: bdflag = B4800; break; case 9600: bdflag = B9600; break; case 19200: bdflag = B19200; break; case 38400: bdflag = B38400; break; case 57600: bdflag = B57600; break; case 115200: bdflag = B115200; break; case 230400: bdflag = B230400; break; case 460800: bdflag = B460800; break; case 500000: bdflag = B500000; break; case 576000: bdflag = B576000; break; case 921600: bdflag = B921600; break; case 1000000: bdflag = B1000000; break; case 1152000: bdflag = B1152000; break; case 1500000: bdflag = B1500000; break; case 2000000: bdflag = B2000000; break; case 2500000: bdflag = B2500000; break; case 3000000: bdflag = B3000000; break; case 3500000: bdflag = B3500000; break; case 4000000: bdflag = B4000000; break; default: fprintf (stderr, "Unsupported baudrate %i\n", baudrate); fprintf (stderr, "Supported are: 0, 50, 70, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200,\n"); fprintf (stderr, "38400, 57600, 115200, 230400, 460800, 500 k, 576 k, 921.6 k, 1 M, 1.152 M, 1.5 M, 2 M, 2.5 M,\n"); fprintf (stderr, "3 M, 3.5 M and 4 M.\n"); exit (-1); } // open and check the file descriptor with the read+write mode and waitig mode fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY); fprintf (stderr, "got file descriptor fd=%d\n", fd); /* The O_NOCTTY flag tells UNIX that this program doesn't want to be the "controlling terminal" for that port. If you don't specify this then any input (such as keyboard abort signals and so forth) will affect your process. Programs like getty(1M/8) use this feature when starting the login process, but normally a user program does not want this behavior. The O_NDELAY flag tells UNIX that this program doesn't care what state the DCD signal line is in - whether the other end of the port is up and running. If you do not specify this flag, your process will be put to sleep until the DCD signal line is the space voltage. */ if (0 >= fd) { perror ("open failed"); exit (-1); } if (!isatty (fd)) { fprintf (stderr, "The Device %s is no Terminal-Device !\n", device); return (-1); } fprintf (stderr, "open %s done.\n", device); //fcntl (fd, F_SETFL, 0); tcgetattr (fd, &tty); tty.c_iflag = IGNBRK | IGNPAR; // ignore BREAKS at input tty.c_oflag = 0; // raw output tty.c_lflag = 0; // nothing special tty.c_line = 0; tty.c_cc[VTIME] = 0; // no waiting tty.c_cc[VMIN] = 1; // minimum of reading bytes, was 1 tty.c_cflag = CREAD | CLOCAL | HUPCL; // enable receive, no modem status, // Even parity (7E1): tty.c_cflag |= PARENB; // parity tty.c_cflag &= ~PARODD; // even parity tty.c_cflag &= ~CSTOPB; // 1 stop bit tty.c_cflag |= CS7; // 7 bit per byte tty.c_cc[VMIN] = 0; // wait for at least 0 characters // No Echo, no control characters, no Interrupts tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); cfsetispeed (&tty, bdflag); // input cfsetospeed (&tty, bdflag); // output fprintf (stderr, "cfsetispeed and cfsetospeed done.\n"); if (-1 == tcsetattr (fd, TCSAFLUSH, &tty)) { fprintf (stderr, "Error: Could not set terminal attributes for %s !\n", device); } // Set IO-Control-Parameter and check the device. if (-1 == ioctl (fd, TIOCEXCL, &modelines)) // Put the tty into exclusive mode. { fprintf (stderr, "Error at ioctl TIOCEXCL on %s!\n", device); perror ("ioctl()"); return (-1); } fprintf (stderr, "ioctl done.\n"); return (fd); } // open_serial // set ready to sent and data terminal ready; seems to be necessary for the opto coupler void set_rts_dtr (int fd) { int arg = TIOCM_RTS | TIOCM_DTR; ioctl (fd, TIOCMBIS, &arg); arg = TIOCM_RTS; ioctl (fd, TIOCMBIC, &arg); return; } // set_rts_dtr // simple timeout void * func_timeout (void *threadid) { sleep (5); // 5 s timeout if (0 >= fd) // device file could not be opened { fprintf (stderr, "Error: Timeout, device file could not be opened, exiting.\n"); exit (-1); } pthread_exit (NULL); } // func_timeout // print the actual time and date to stderr void write_times (void) { struct tm *tm; long long int tf; time_t t1 = time (NULL); tm = localtime (&t1); // tf in 100 ms tf = mc_POS_DIV ((get_time (NULL) - g_t), 100000); fprintf (stderr, " Time %0.3f, local date and time: %d-%d-%d, %d:%d:%d.\n", tf / 10., tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return; } // write_times // Cleartext output of the input buffer. Parameter: Flag for // stdout/stderr as standard out. void dump0 (unsigned char *buffer, _Bool b_channel) { //int n; // unsigned char buffer1[9] = { 0 }; //float it = 0.; long long int tf = 0; //char units[20] = { 0 }; // time in tenths of a second (100 ms) tf = mc_POS_DIV ((get_time (NULL) - g_t), 100000); fprintf ((b_channel ? stderr : stdout), "%0.1f %s", tf / 10., buffer); fflush (stdout); return; } // dump0 int main (int argc, char **argv) { const char *device = argv[1]; // serial port, e. g. /dev/ttyS0 or /dev/ttyUSB0 static unsigned char buffer[0x100]; signed int i, n; // calculate deadline (timeout) in µs for receiving 1 Byte: 3 times transmission time of a byte (+stop bit +start bit: 10 bit) // (3 * 10 * 1000000) / 1200 = 25000 for fast USB-RS232-Adapters, // 25000 for slow USB-RS232-Adapters, // 50000 for slow onboard-ports const int i_deadline = 50000; // 50 ms long long int t = 0; int maxfd = 0, id = 0, i_ret = 0, i_retval; static struct timeval timeout; static fd_set readfs; // file descriptor set static pthread_t thread; signed long long int lli_rec_time = 0, timeval0 = 0, timeval1 = 0; struct tm *tm; time_t t1 = 0; if ((argc > 1) and not strncmp (argv[1], "-V", 123)) { fprintf (stdout, "%s version %s\n", argv[0], SOFTWARE_VERSION_NUMBER); exit ((2 == argc) ? 0 : -1); } if (argc < 2) { fprintf (stderr, "Usage: %s \n", argv[0]); fprintf (stderr, "\aor: %s -V \n", argv[0]); fprintf (stderr, "[] = Optional <> = Parameter Requirement. 1 parameter required.\n"); exit (-1); } // create a thread for timeout (to avoid hangup in open_serial, e. g. when the device can not be opened) i_ret = pthread_create (&thread, NULL, func_timeout, (void *) &id); if (i_ret) { fprintf (stderr, "ERROR; return code from pthread_create() is %d\n", i_ret); exit (-1); } fd = open_serial (device, 1200); maxfd = fd + 1; // maximum entry if (fd <= 0) { fprintf (stderr, "Error: Could not open port, exiting.\n"); exit (-1); } set_rts_dtr (fd); // DTR/RTS setzen tcflush (fd, TCIOFLUSH); // flush buffers write_times (); // print time/date t1 = time (NULL); tm = localtime (&t1); fprintf (stdout, "# start date and time: %d-%d-%d, %d:%d:%d.\n", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); t = get_time (NULL); g_t = t; // init lcr612: do it manually ... for (;;) // for every data packet { // request data buffer[0] = 'N'; i = write (fd, buffer, 1); if (1 not_eq i) fprintf (stderr, "Error: Could write only %d of 1 byte(s), ignoring.\n", i); // init n = 0; timeval0 = 0; memset (buffer, 0, sizeof (buffer)); //tcdrain (fd); // wait till the output has been transmitted; not really necessary for (;;) // receive the bytes one by one { memset (&timeout, 0, sizeof (timeout)); timeout.tv_sec = 2; // seconds FD_ZERO (&readfs); FD_SET (fd, &readfs); i_retval = select (maxfd, &readfs, NULL, NULL, &timeout); if (0 != i_retval) { if (-1 == i_retval) { perror ("select()"); usleep (50000); // wait 50 ms tcflush (fd, TCIOFLUSH); // flush buffers break; } i_retval = read (fd, &buffer[n], 1); // number of received bytes if (-1 == i_retval) // read error { fprintf (stderr, "Error: read returned -1.\n"); break; } // check if the time from Byte n-1 to n is not too great; the data packet must be defragmented if (n) { // time left i = timeout.tv_usec + timeout.tv_sec * 1000000; i = 2000000 - i; // receive time if (i > i_deadline) { fprintf (stderr, "Received data packet fragment: Receive time (in µs) %d > deadline %d, Byte %d.\n", i, i_deadline, n); write_times (); dump0 (buffer, 1); n = 0; // nothing valid received usleep (50000); // wait 50 ms tcflush (fd, TCIOFLUSH); // flush buffers break; // restart receiving } } n += i_retval; // add number of received bytes // end if data are complete if (39 <= n) break; if (0 == timeval0) // start time counter for reading data { timeval0 = get_time (NULL); // store the time of the actual Byte // check if the leap to the last data packet was long enough lli_rec_time = timeval0 - timeval1; if (lli_rec_time < 100000) // less than 100 ms leap { fprintf (stderr, "Too small leap between the data packets: Only %lld µs.\n", lli_rec_time); write_times (); dump0 (buffer, 1); n = 0; // nothing valid received usleep (50000); // wait 50 ms tcflush (fd, TCIOFLUSH); // flush buffers } } } else { fprintf (stderr, "Connection timeout (select failed).\n"); write_times (); break; } } // for (;;) if (0 == n) // nothing valid received continue; if (39 < n) fprintf (stderr, "Warning: The data packet has %d bytes (should be 39).\n", n); timeval1 = get_time (NULL); // store the actual time of the actual data packet end lli_rec_time = timeval1 - timeval0; // more than 100 ms or less than 25 ms: invalid data if ((lli_rec_time > 500000) or (lli_rec_time < 30000)) { fprintf (stderr, "Receive timeout, invalid data packet (took %lld microseconds and must be between 30000 and 500000).\n", lli_rec_time); write_times (); dump0 (buffer, 1); continue; } if (n < 39) // no full packet received { fprintf (stderr, "Error: Only %d Bytes in the actual data packet. Ignoring.\n", n); write_times (); dump0 (buffer, 1); continue; } // check the last two bytes and print the data if ((0xd not_eq buffer[37]) or (0xa not_eq buffer[38])) { fprintf (stderr, "Error: Corrupted data.\n"); dump0 (buffer, 1); continue; } // print the data to stdout dump0 (buffer, 0); //usleep(10000); // wait 10 ms for next reading/writing } pthread_kill (thread, 15); // terminate timeout thread pthread_exit (NULL); // exit (0); } // main