Search code examples
c++unixserial-portioctl

C++ Serial port reading issue: does ioctl(FIONREAD) set a wrong value?


I am facing a very strange problem, which I wasn't able to solve. I want to read (just read) data collected and sent by a micro-controller via usb as serial port (FTDI) on Mac Os X using c++. The the size of one complete data-sequence is always exactly 10 bytes. However I was using the following code to read the data:

Imported libraries:

#include <iostream>
#include <fstream>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>

Code:

void init(){
    serial = open(port.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); // 0_RDONLY ?
    struct termios options;
    //set opt to 115200-8n1
    cfsetspeed(&options, B115200);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    tcsetattr(serial, TCSANOW, &options);
    if (serial < 0){
        //Error
    }else{
        //run loop
    }
}

void serial_loop(){
    long bytes_read;
    int bytes_available;
    unsigned char msg[10];
    while(1){
        do{
            usleep(1000);
            ioctl(serial, FIONREAD, &bytes_available);

        }while(bytes_available < 10); //wait for the sequence to complete

        bytes_read = read(serial, msg, 10);

        //do some parsing here
    }
}

This code worked a few days ago but now it isn't anymore. The data is reaching the computer perfectly according to the Terminal -> screen -command. I checked the port-file-name which is still correct and the port is opened successfully as well. I narrowed down my issue to the ioctl-command FIONREAD which doesn't write the correct number to the bytes_available-var (anymore). It did work, and I believe, I didn't change anything in the code.

Do you see any problem that could cause this issue? Are there any dangerous passages in my code? Thank you for your help, I'm really stuck here...

EDIT: Thanks to the feedback, I was able to get it running again. Here is the current code:

int serial;
void init(){
    serial = open(port.c_str(), O_RDWR | O_NOCTTY); //removed 0_NONBLOCK
    struct termios options;
    //set opt to 115200-8n1
    cfsetspeed(&options, B115200);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //Non-canonical
    options.c_cc[VMIN]     = 1; //block read until at least 1 byte was recieved
    options.c_lflag = 0;

    tcsetattr(serial, TCSANOW, &options);
    if (serial < 0){
        //Error
    }else{
        //run loop
    }
}

void serial_loop(){
    int datalength = 10;
    long bytes_read = 0;
    int bytes_in_msg = 0;
    unsigned char buf[datalength];
    unsigned char msg[datalength];
    do{
        bytes_read = read(serial, buf, datalength-bytes_in_msg);
        usleep(1000);
        if (bytes_read>0){
            memcpy(&msg[bytes_in_msg], &buf, bytes_read);
        }
        bytes_in_msg += bytes_read;
    }while(bytes_in_msg < datalength);

    //do some parsing here
    }
}

This works, but is there anything left, that could be problematic?

Thank you for your support!


Solution

  • This code worked a few days ago but now it isn't anymore.

    Fickle program behavior usually indicates improper or incomplete initialization.
    Your termios initialization only configures the baudrate and character framing, and everything else is left to chance.
    See Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems.


    Your revised code still has not properly resolved this issue.
    The code never initializes the termios structure options by calling the tcgetattr() function.
    For sample code see my answer to How to open, read, and write from serial port in C?


    options.c_lflag = 0;
    

    That is not considered the proper way of assigning a termios element.


    options.c_cc[VMIN]     = 1;
    

    Non-canonical mode requires definition of both the VMIN and VTIME entries.
    Your code uses whatever garbage value that exists at the location for VTIME.
    See Linux Blocking vs. non Blocking Serial Read.


    FIONREAD which doesn't write the correct number to the bytes_available-var (anymore).

    Negative descriptions, i.e. what does not occur, are not as helpful or specific as descriptions of what does occur.
    So what kind of values are you getting back?
    Why do you think it is "wrong"?

    More importantly, why aren't you checking the return value from each syscall, especially this ioctl() that you think is giving you problems?

    Most likely is that the ioctl() failed, did not update your bytes_available variable, and returned an error code.
    Instead of first checking for a good return, your code unconditionally uses the returned argument.


    Another answer that critiques your code is misleading. Flow control can be disabled. Your code is broken because it doesn't do proper initialization and is not checking for error returns, not because the comm link is "deadlocked".


    In your revised code:

        bytes_read = read(serial, buf, datalength-bytes_in_msg);
        usleep(1000);
    

    A sleep or delay before and/or after a blocking read is superfluous.