0

I have been trying to read the responses from a serial temperature sensor interfaced to my raspberry pi using a USB to serial converter.

I can see that the writes to the sensor device seem to work. However when I try to read back from the serial chip the read fails with -1.

I did try to use the same baud rate 9600 8 bit no parity settings using realterm program and was able to read and write hex values as expected, kindly point me in the right direction.

void serial_write(char parameter,char value) {
    int fd;
    uint8_t bytes_wr;
    char wr_buffer[3];
    fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NDELAY); 

    if (fd == -1)
        ERROR("Error! in Opening ttyUSB0 \n");
    else
        DEBUG("ttyUSB0 Opened Successfully \n");

    struct termios SerialPortSettings;
    tcgetattr(fd, &SerialPortSettings);

    cfsetispeed(&SerialPortSettings,B9600);
    cfsetospeed(&SerialPortSettings,B9600);

    SerialPortSettings.c_cflag &= ~PARENB;
    SerialPortSettings.c_cflag &= ~CSTOPB;
    SerialPortSettings.c_cflag &= ~CSIZE;
    SerialPortSettings.c_cflag |=  CS8; 
    SerialPortSettings.c_cflag &= ~CRTSCTS;
    SerialPortSettings.c_cflag |= CREAD | CLOCAL;
    SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);  
    SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    SerialPortSettings.c_oflag &= ~OPOST;

    if ((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) 
        ERROR("ERROR ! in Setting attributes \n");
    else
        DEBUG("BaudRate=9600\tStopBits=1\tParity=none \n");

    wr_buffer[0] = write;
    wr_buffer[1] = parameter;
    wr_buffer[2] = value;

    bytes_wr = write(fd, wr_buffer,sizeof(wr_buffer));
    DEBUG("Total Bytes written: %d \n", sizeof(wr_buffer));

    close(fd);
}

The above function seems to write as expected to the serial port, however when I try to read, the reads fails with a -1

char serial_read(char parameter) {
    int fd, read_length, i;
    uint8_t bytes_wr;
    char wr_buffer[2];
    fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NDELAY); 

    if (fd == -1)
        ERROR("Error! in Opening ttyUSB0 \n");
    else
        DEBUG("ttyUSB0 Opened Successfully \n");

    struct termios SerialPortSettings;
    tcgetattr(fd, &SerialPortSettings);

    cfsetispeed(&SerialPortSettings,B9600);
    cfsetospeed(&SerialPortSettings,B9600);

    SerialPortSettings.c_cflag &= ~PARENB;
    SerialPortSettings.c_cflag &= ~CSTOPB;
    SerialPortSettings.c_cflag &= ~CSIZE;
    SerialPortSettings.c_cflag |=  CS8; 
    SerialPortSettings.c_cflag &= ~CRTSCTS;
    SerialPortSettings.c_cflag |= CREAD | CLOCAL;
    SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);  
    SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    SerialPortSettings.c_oflag &= ~OPOST;

    if ((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) 
        ERROR("ERROR ! in Setting attributes \n");
    else
        DEBUG("BaudRate=9600\tStopBits=1\tParity= none\n");

    wr_buffer[0] = read;
    wr_buffer[1] = parameter;

    bytes_wr = write(fd, wr_buffer,sizeof(wr_buffer));
    DEBUG("Total Bytes written: %d \n", sizeof(wr_buffer));
    usleep(8000);
    tcflush(fd,TCIFLUSH);
    char rd_buffer[4];
    read_length = read(fd, rd_buffer,sizeof(rd_buffer));
    DEBUG("Total bytes read = %d \n",read_length);

    for (i==0;i<read_length;i++){
        DEBUG("rd_buffer[%d]=%x \n",i,rd_buffer[i]);
    }
    close(fd);
    return rd_buffer[0];
}

With realterm windows application all writes and reads seem to work fine.

tzrm
  • 513
  • 1
  • 8
  • 14
A.R
  • 13
  • 4
  • Why usleep? Why tcflush? 'return rd_buffer;' - no, returning a local array is UB – Martin James Dec 24 '18 at 14:18
  • Hi Sorry My actual program returnd rd_buffer[0] . I tried to remove tcflush to remove the previous data stored in the buffer. I initially did not have a sleep but i saw some examples where they stated that we have to wait for the data to be written before read. – A.R Dec 24 '18 at 14:23
  • Your code has bugs (because you copied from bad example(s)). Your termios initialization is incorrect and incomplete; see https://stackoverflow.com/questions/51195829/ubuntu-serial-communication-reads-failing-and-then-coming-in-all-at-once/51201877#51201877 Your use of **tcflush()** instead of **tcdrain()** is problematic. Your choice of non-blocking I/O is not properly handled. Use blocking I/O if you don't understand the requirements. – sawdust Dec 24 '18 at 21:54

1 Answers1

1

From the open(2) manpage:

   O_NONBLOCK or O_NDELAY
          When possible, the file is opened in nonblocking mode.
          Neither the open() nor any subsequent operations on the file
          descriptor which is returned will cause the calling process to
          wait.

For a serial connection, the end result will be that if you ask to read some number of bytes from the serial port and there are no characters waiting, then read will return with -1 and 'errno' will probably be EAGAIN or EWOULDBLOCK.

So your usleep(8000) was probably an attempt to wait long enough for the device to respond but the device may not have data for you; especially if it is in the middle of an adc operation, it might take longer than 8ms.

There are a few things you can do:

You can (in pseudo code):

int retries=10;
while(retries--) {
    read_length = read(fd, rd_buffer,sizeof(rd_buffer));
    if(read_length > 0)
        break;
    usleep(1000);
}

Unfortunately, one side effect of this is that if the temperature sensor is sending you a lengthy string and your program read()s while the temperature sensor is still writing, you will get a partial string. So if you know the length of string that you're waiting to receive, you could use an ioctl() to find out how many characters are waiting:

ioctl(fd, FIONREAD, &bytes_avail);

So the pseudo code would look more like:

int retries=10;
int bytes_avail=0;
while(retries--) {
    if (ioctl(fd, FIONREAD, &bytes_avail) < 0) {
        fprintf(stderr, "ioctl failed\n");
        return;   // Do something here
    }
    if (bytes_avail >= sizeof(rd_buffer)) {
        read_length = read(fd, rd_buffer,sizeof(rd_buffer));
        if(read_length > 0)
            break;
    }
    usleep(1000);
}

If the temperature sensor sends an ascii string that is terminated with a newline or carriage-return, then the code would look different.

MumbleBuns
  • 19
  • 1
  • is there a deterministic way to get reads work consistently. I see there is a caveat in using method 1 where I might end up getting partial strings and I do not know the length of the strings I am waiting(it varies from parameter to parameter) for so I may not be able to use method 2 either. – A.R Dec 24 '18 at 15:44
  • Linux is an interrupt/event-driven OS, yet you advocate using polling? Using non-blocking I/O and then polling/sleeping to check if data is available in the system buffer is a misuse of the technique and inefficient. Blocking I/O would be more practical and efficient. And that's only one part of the problems with the OP's code. – sawdust Dec 24 '18 at 23:50
  • There was a lot of problems with what OP was trying to do. I was answering the question of reads returning -1. Maybe OP is calling serial_read() from a thread or what have you. That strikes me as being outside the scope of the question. – MumbleBuns Dec 25 '18 at 14:43