Thread: Best way to read character input from serial port (Linux)?

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Registered User
    Join Date
    Jun 2009
    Posts
    101

    Best way to read character input from serial port (Linux)?

    I've got an app that reads characters from a serial port, evaluates these characters and then responds if necessary. I have it working somewhat, but it's not totally reliable. The main issue is that I have a while loop that constantly reads input but I can't figure out how to determine when new characters are sent -- it just reads constantly, sets a variable and outputs it. So, if the master device sends two characters, "0x61, 0x20", My output looks like this:

    61
    20
    20
    20
    20
    20
    ...and so on forever, since the var that holds the input hasn't been reset.

    The bigger issue is that depending on the timing of the while loop, old characters may get mixed up with new input from the master device, which throws everything off. I need the loop to STOP outputting until it knows for sure that new characters are being sent.

    Example:

    Code:
    int fd_serialport;
    struct termios options;
    
    fd_serialport = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
    
    if(fd_serialport == -1){
    	perror("Unable to open /dev/ttyS0");
    }
    
    tcgetattr(fd_serialport, &options);
    cfsetispeed(&options, B38400);
    cfsetospeed(&options, B38400);
    options.c_cflag |= (CLOCAL | CREAD);	
    options.c_cflag |= PARENB;
    options.c_cflag |= PARODD;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    options.c_iflag |= (INPCK | ISTRIP);
    tcsetattr(fd_serialport, TCSANOW, &options);
    	
    fcntl(fd_serialport, F_SETFL, FNDELAY);
    
    while(read(fd_serialport, &data_in[0], sizeof(char))){
    		
    	printf("%s\n",&data_in[0]);
    	usleep(2000);
    }

  2. #2
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    Ok, I think I partially figured this out. If I automatically set the variable to '\0' after reading one character, and then add an if statement to check this, it seems to work the way I need it. HOWEVER, there are characters sent that actually are 0 (0x00), that I need to see. If I set the var to '\0' after reading, when input is read that actually is 0 (and valid), these characters are ignored:

    Code:
    while(read(fd_serialport, &data_in[0], sizeof(char))){
    		
    	if(data_in[0] != '\0'){
    		printf("%02x\n",data_in[0]);
    	}
    	data_in[0] = '\0'; //reset and wait for next char
    }

  3. #3
    ATH0 quzah's Avatar
    Join Date
    Oct 2001
    Posts
    14,826
    Code:
    while( read( fd_serialport, &data[0], 1 ) != -1 )
    {
        ...do stuff...
    }
    You're better off looping until it generates an error, or until you've read some other exit condition. How do you know the serial port isn't going to read the same byte twice, and that it's OK to do so, because that's what's really being sent?


    Quzah.
    Hope is the first step on the road to disappointment.

  4. #4
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    I think I figured it out. I used select() to determine if new data was available (by checking FD_ISSET), then read(). It seems to be working OK.

  5. #5
    Registered User
    Join Date
    Oct 2008
    Location
    TX
    Posts
    2,059
    Another way is to setup the read() to block until new characters are available in the serial input buffer:
    Code:
    fcntl(fd_serialport, F_SETFL, 0);         /* causes read to block until new characters are present */
    fcntl(fd_serialport, F_SETFL, FNDELAY);   /* remove this call as it causes read to return immediately */

  6. #6
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    Quote Originally Posted by itCbitC View Post
    Another way is to setup the read() to block until new characters are available in the serial input buffer:
    Code:
    fcntl(fd_serialport, F_SETFL, 0);         /* causes read to block until new characters are present */
    fcntl(fd_serialport, F_SETFL, FNDELAY);   /* remove this call as it causes read to return immediately */
    Ah, I see what's going on here. Opening a file descriptor with the O_NONBLOCK flag set won't wait for input before continuing the loop. That's why I had to use select(), which I'm realizing now was unnecessary. Thanks for clearing that up.

    I've also discovered the following:

    If using non-blocking I/O, you will need to implement your own checks to confirm the completion of reads/writes. I've found a few ways of doing this. One is the way you just mentioned (which seems to be the simplest), using read() with blocking I/O flags set. Second is using a non-blocking file descriptor with select() to check for completed reads. Yet another is using a non-blocking file descriptor flagged with O_ASYNC along with signal(), checking for SIGIO:

    Code:
    fcntl(fd_serialport, F_SETOWN, getpid());
    fcntl(fd_serialport, F_SETFL, O_ASYNC);
    I think I'll just stick with plain old blocking mode. It seems to work reliably.

  7. #7
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by synthetix View Post
    Ah, I see what's going on here. Opening a file descriptor with the O_NONBLOCK flag set won't wait for input before continuing the loop. That's why I had to use select(), which I'm realizing now was unnecessary. Thanks for clearing that up.
    So you took the good solution (select) and replaced it with a dumb one (non-blocking IO).
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  8. #8
    ATH0 quzah's Avatar
    Join Date
    Oct 2001
    Posts
    14,826
    Using select will allow you to sleep when nothing is waiting to be read.


    Quzah.
    Hope is the first step on the road to disappointment.

  9. #9
    ATH0 quzah's Avatar
    Join Date
    Oct 2001
    Posts
    14,826
    If you're not sleeping for some length of time, then you are going to be just check check check check check check check check check check ... as fast as it can, burning up 100% of your CPU time. If one thread is sitting there doing nothing, where the other one is going to say "check check check check...", your scheduler should look and say "Hey, that thread's not doing anything, and this one needs an assload of CPU time, let's let it do its thing..."

    Basically, if nothing is pending on either thread, you want to not really do anything at all (assuming you're not actually trying to do anything while you don't have any data), other than sit and wait for something to happen.


    Quzah.
    Hope is the first step on the road to disappointment.

  10. #10
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    The serial port "module" runs in its own thread, using non-blocking I/O (O_NONBLOCK) and select(). I passed a struct to the thread that contains the stuff that the thread needs to update (based on serial input) for the purpose of controlling the main thread:

    Code:
    void		*do_serial_control(void *args); //prototype for ctrl thread
    
    struct SERIALCTRL {
    	int	device_status;
    	char	timecode[4];
    };
    
    struct		SERIALCTRL serial_control;
    
    pthread_create(&thread1, NULL, do_serial_control, (void*)&serial_control);
    do_serial_control() communicates with the main thread by taking serial port input and updating vars in the main thread via the the struct. These vars are constantly checked in the main loop.

    Seems to be working well so far! Thanks for the help!

  11. #11
    Registered User
    Join Date
    Oct 2008
    Location
    TX
    Posts
    2,059
    Curious if you made it non-blocking by setting a timeout in the struct timeval member??

  12. #12
    Registered User
    Join Date
    Jun 2009
    Posts
    101
    Quote Originally Posted by itCbitC View Post
    Curious if you made it non-blocking by setting a timeout in the struct timeval member??
    I didn't have to do this because the machine is being queried via the serial port constantly. So, I just passed NULL as the last argument to select(). From what I can tell, this effectively throttles the reads off the serial port based on the speed that the remote machine is sending queries. And since it's running in its own thread, this doesn't tie up the main thread, which is constantly checking variables updated from the serial port reader thread (via a loop) and performing the main program functions.

  13. #13
    Registered User
    Join Date
    Oct 2008
    Location
    TX
    Posts
    2,059
    Quote Originally Posted by synthetix View Post
    I didn't have to do this because the machine is being queried via the serial port constantly. So, I just passed NULL as the last argument to select(). From what I can tell, this effectively throttles the reads off the serial port based on the speed that the remote machine is sending queries. And since it's running in its own thread, this doesn't tie up the main thread, which is constantly checking variables updated from the serial port reader thread (via a loop) and performing the main program functions.
    Reason to have a timeout is to prevent the select from blocking otherwise it'll just sit there and wait for input exactly like the call below:
    Code:
    fcntl(fd_serialport, F_SETFL, 0);
    Is your application doing anything else besides reading from that serial port? Are there many ports to read from?
    The power of select() is in its ability to poll each file descriptor in turn, waiting for an event or else timing out.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. sending data over UDP from serial port
    By forumguy in forum Linux Programming
    Replies: 0
    Last Post: 04-25-2009, 02:10 PM
  2. can someone help me with these errors please code included
    By geekrockergal in forum C Programming
    Replies: 7
    Last Post: 02-10-2009, 02:20 PM
  3. Problem with string and serial port
    By collinm in forum C Programming
    Replies: 2
    Last Post: 03-23-2005, 10:19 AM
  4. Serial Communications in C
    By ExDigit in forum Windows Programming
    Replies: 7
    Last Post: 01-09-2002, 10:52 AM
  5. Need help or info about serial port communication
    By Unregistered in forum Linux Programming
    Replies: 1
    Last Post: 01-08-2002, 01:48 PM