2

I am trying to write and read data from an USB port in a cygwin command central. I have managed to write and read data when the device is connected but I want a way to detect if the other device isn't present or is unable ta send back any data. My current test code is shown below(I have tried a bunch of different things but nothing seemed to work).

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <stdio.h>
#include "USB_com.h"
#include "unistd.h"
void main()
{
  int fd, value, bytes_read, bytes_written, nbytes, i, j;
  char buffR[20];
  char buffS[20];
  fd = USB_init("/dev/com1");
  printf("enter a message (write exit to terminate the session): ");
  fgets(buffS, 19, stdin);
  while (strncmp("exit", buffS, 4) != 0)
  {
    bytes_written = write(fd, buffS, 19);
    sleep(1);
    bytes_read = read(fd, buffR, 19);
    printf("string recieved : %s\n", buffR);
    memset(buffS, '\0', 19);
    printf("enter a message (write exit to terminate the session): ");
    fgets(buffS, 19, stdin);
  }
  USB_cleanup(fd);
}

And my USB_init.c for writing and reading from the USB device is shown below.

#include "USB_init.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdlib.h>
#include <strings.h>
#include <stdio.h>


/* baudrate settings are defined in <asm/termbits.h>, which is
 * included by <termios.h> */
#ifndef BAUDRATE
#define BAUDRATE B9600
#endif

#define _POSIX_SOURCE 1     /* POSIX compliant source */

static int fd, c, res;
static struct termios oldtio, newtio;
static char *device;

int USB_init(char *modemdevice)
{
    /* 
     * Open modem device for reading and writing and not as controlling tty
     * because we don't want to get killed if linenoise sends CTRL-C.
     **/
    device = modemdevice;
    //fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY);
    fd = open (device, O_RDWR | O_NOCTTY );
    if (fd < 0)
      {
      perror (device);
      exit(-1);
      }

    tcgetattr (fd, &oldtio);    /* save current serial port settings */
    bzero (&newtio, sizeof (newtio));   /* clear struct for new port settings */

    /* 
     *BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
     *CRTSCTS : output hardware flow control (only used if the cable has
     *all necessary lines. )
     *CS8     : 8n1 (8bit,no parity,1 stopbit)
     *CLOCAL  : local connection, no modem contol
     *CREAD   : enable receiving characters
     **/
    newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;

    /*
     *IGNPAR  : ignore bytes with parity errors
     *ICRNL   : map CR to NL (otherwise a CR input on the other computer
     *          will not terminate input)
     *          otherwise make device raw (no other input processing)
     **/
    newtio.c_iflag = IGNPAR | ICRNL;

    /*
     * Map NL to CR NL in output.
     *                  */
#if 0
    newtio.c_oflag = ONLCR;
#else
    newtio.c_oflag = 0;
#endif


    /*
     * ICANON  : enable canonical input
     *           disable all echo functionality, and don't send signals to calling program
     **/
#if 1
    newtio.c_lflag = ICANON;
#else
    newtio.c_lflag = 0;
#endif

    /* 
     * initialize all control characters 
     * default values can be found in /usr/include/termios.h, and are given
     * in the comments, but we don't need them here
     *                                       */
    newtio.c_cc[VINTR] = 0; /* Ctrl-c */
    newtio.c_cc[VQUIT] = 0; /* Ctrl-\ */
    newtio.c_cc[VERASE] = 0;    /* del */
    newtio.c_cc[VKILL] = 0; /* @ */
    newtio.c_cc[VEOF] = 4;  /* Ctrl-d */
    newtio.c_cc[VTIME] = 0; /* inter-character timer unused*/
    newtio.c_cc[VMIN] = 1;  /* blocking read until 1 character arrives*/
    newtio.c_cc[VSWTC] = 0; /* '\0' */
    newtio.c_cc[VSTART] = 0;    /* Ctrl-q */
    newtio.c_cc[VSTOP] = 0; /* Ctrl-s */
    newtio.c_cc[VSUSP] = 0; /* Ctrl-z */
    newtio.c_cc[VEOL] = 0;  /* '\0' */
    newtio.c_cc[VREPRINT] = 0;  /* Ctrl-r */
    newtio.c_cc[VDISCARD] = 0;  /* Ctrl-u */
    newtio.c_cc[VWERASE] = 0;   /* Ctrl-w */
    newtio.c_cc[VLNEXT] = 0;    /* Ctrl-v */
    newtio.c_cc[VEOL2] = 0; /* '\0' */

    /* 
     * now clean the modem line and activate the settings for the port
     **/
    tcflush (fd, TCIFLUSH);
    tcsetattr (fd, TCSANOW, &newtio);

    /*
     * terminal settings done, return file descriptor
     **/

    return fd;
}

void USB_cleanup(int ifd){
    if(ifd != fd) {
        fprintf(stderr, "WARNING! file descriptor != the one returned by serial_init()\n");
    }
    /* restore the old port settings */
    tcsetattr (ifd, TCSANOW, &oldtio);
}

Can someone tell me how I can do something like read(fd,buffR,19) but abort it after some time if I haven't recieved any data and print something like printf("no contact with the device")?

I am extremely thankful for any suggestions on how to solve this!

Henrik
  • 405
  • 1
  • 8
  • 19
  • *"I am trying to write and read data from an USB port"* -- If you're using a USB-to-RS232 adapter (or a USB gadget that emulates a serial port device), then the fact that the USBus is involved is totally transparent to the application program. So naming your functions **USB_init()** and **USB_cleanup()** when they do nothing with respect to USB is misleading. `bzero (&newtio, sizeof (newtio))` is a common but wrong method of initializing a **termios** structure. Study [Setting Terminal Modes Properly](http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_12.html#SEC237) – sawdust May 26 '14 at 01:44
  • @sawdust - you are of course generally right about the misuse of the USB label with what is a serial API, but it is worth remembering that USB serial converters are not, in fact totally transparent - they are packetized transport devices with substantial latency, a difference which can have practical impact on application programs and protocols. – Chris Stratton May 27 '14 at 12:55

2 Answers2

4

When working with serials, you have two options: either use select/poll with timeout, or configure the port using termios.

For select and poll, the approach is first wait for event on descriptor and read after successful result. Pro of this method is that it works also with network sockets and some other descriptor types:

int fd = ...
fd_set fds;
stuct timeval timeout;

timeout.tv_sec = 10; /* timeout in secs */
timeout.tv_usec = 0;
FD_ZERO(&fds)
FD_SET(fd, &fds)
if (select(fd, fds, NULL, NULL, &timeout) > 0)
  read(...)
else ... timeout

The poll example is very similar to above.

But, when working with any kind of serial devices, there is another way of making read to time out correctly. You need to use VMIN and VTIME attributes:

newtio.c_cc[VTIME] = timeout; /* timeout in 1/10 of second  1==100ms, 10==1sec*/
newtio.c_cc[VMIN] = 0;

That is it.

Valeri Atamaniouk
  • 5,125
  • 2
  • 16
  • 18
  • Second part of answer is incomplete and misleading. **VMIN** and **VTIME** are only salient when non-canonical input mode is used. – sawdust May 26 '14 at 01:39
  • @sawdust What do you mean by non-canonical input mode? – C-- Aug 09 '18 at 13:22
  • @SubinSebastian -- See https://stackoverflow.com/questions/25996171/linux-blocking-vs-non-blocking-serial-read/26006680#26006680 – sawdust Aug 09 '18 at 19:48
  • Anyone using this in a loop should be wary to reset ```timeout.tv_sec``` and ```timeout.tv_usec``` each iteration. The last paragraph of [select(2) The timeout](http://man7.org/linux/man-pages/man2/select.2.html#DESCRIPTION) details how Linux alters these values. – jmcker Nov 07 '18 at 09:19
2

You might like to take a look at select() to monitor the file descriptor for being ready to be read from. It could be called with a time-out.

Or make the file describtor non-blocking when open()ing it or later using fcntl(), so it will return from read() with EAGAIN or EWOULDBLOCK if no data is available, which would allow you to implement your on logic to decide when to retry or to cancel reading.

alk
  • 69,737
  • 10
  • 105
  • 255
  • It says ` Note that this flag has no effect for regular files and block devices;` http://man7.org/linux/man-pages/man2/open.2.html – shaoyihe Oct 14 '18 at 09:03
  • @shaoyihe: Linking the Linux man-pages in a Cygwin context probably was not a good idea. The POSIX documentation is here: http://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html It specifies the case of non-/blocking differently. – alk Oct 14 '18 at 09:16