Thread: Problem with Serial I/O to RS-232 Port

  1. #1
    Registered User
    Join Date
    Oct 2010
    Posts
    9

    Problem with Serial I/O to RS-232 Port

    I am opening a /dev/ttyUSB0 device using fopen using mode "w+". I need to both read and write from the device. I have set the port to raw mode (using tcgetattr, cfmakeraw, and tcsetattr). I use function fputc and fgetc to do my input and output a character at a time. I initially write to the port bytes. Then I receive bytes using fgetc. I can receive all the bytes I want, but once I try write again on usng fputc (which succeeds), then my call to fgetc return EOF and errorno says "Illegal Seek".

    Does anyone know why doing a write to a serial port causes future reads to fail? It fails most of the time (sometimes it succeeds).

  2. #2
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    This sounds like you have not configured your port correctly. Post some code and maybe someone might be able to help.


    Jim

  3. #3
    Registered User
    Join Date
    Oct 2010
    Posts
    9
    I have selected just the I/O statements that I think are relavent. I open the file, set the configuration, write a few bytes, then I read data and then write again. It is after I write again when the fgetc fails giving an EOF and producing a errorno of "Illegal seek".

    See my code fragment below:
    Code:
      FILE   *rtxSpaFp;
      int      rtxSpaFd; 
    
      rtxSpaFp = fopen (RTX_SPA_DEVICE, "w+");
      rtxSpaFd = fileno (rtxSpaFp);
      /* Reset the RTX SPA device by sending a break to it */
      tcsendbreak (rtxSpaFd, 0);
      /* Configure RS-232 port for 9600 N81 Raw terminal */
      tcgetattr (rtxSpaFd, &serialPortOptions);
    
      cfmakeraw (&serialPortOptions);
      cfsetispeed (&serialPortOptions, B9600);  /* Start at 9600 */
      cfsetospeed (&serialPortOptions, B9600);
      serialPortOptions.c_oflag &= ~OPOST;    /* Use Raw output */
    
      /* Store updated Terminal options */
      tcsetattr (rtxSpaFd, TCSANOW, &serialPortOptions);
    
      result = fputc (spaCmdWord, rtxSpaFp);
      if (result == EOF)
      {
        cout << "Failed rtx spa send TX 0, exiting" << endl;
        exit(0);
      }
    
          while (1)
    {
          /* get a byte from the serial device */
          result = fgetc(rtxSpaFp);
          if (result == EOF)
          {
            perror("An I/O error has occorred");
            cout << "EOF on RTX SPA device, exiting" << endl;
            exit(0);
          }
    
             result = fputc ((unsigned char)byteSizeBits.to_ulong(), rtxSpaFp); 
    }  /* End while */
    
    I have also tried to use a write statement as well:
             result = write (rtxSpaFd, &spaCmdWord, 1);
    Should I try opening the file with open instead of fopen? I want the program to block indefinately waiting for serial port input. Once it comes in (in the correct sequence), I want to output some bytes and then go back to receive data again. This is a forever while loop. Input, then output, back to input. Should be fairly simple yet something must not be set up correctly. I am using a USB to serial connector. I also tried using a laptop with a built in serial port (i.e. /dev/ttyS0) and it also had the idential behavior so I don't believe the USB device is the issue. When I use the write instead of the fputc, write returns 1 which indicates that it output the byte and fgetc doesn't fail afterword, but I don't see the output data on the serial line, so I don't know where the data is really going.

    Please let me know what you see (if anything).

    Thanks,
    Robert

  4. #4
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    I would use open instead of fopen and then read() and write() get/send data to the port.

    Here is what I normally use to open a port in Linux. (please note that I normally use c++ so you have to change
    the cerr, cout to printf()).

    Code:
       int fd;
    	struct termios old, options;
    	unsigned int baud = B9600;
    
    	// port is a cstring.
    	fd = open(port,O_RDWR | O_NOCTTY | O_NDELAY);
    
    	if(fd == -1)
    	{
    		std::cerr << "ERROR OPENING PORT: " <<  _sys_errlist[errno] << std::endl;
    		return(1);
    	}
    
    	fcntl(fd, F_SETFL,0);
    	// Store the current settings.
    	tcgetattr(fd,&old);
    	// Setup place for new settings.
    	tcgetattr(fd, &options);
            // Insure not owner of port (CLOCAL) enable reading(CREAD).
    	options.c_cflag |= (CLOCAL | CREAD);
            // Set parity, stop bit, bit size (8N1).
           options.c_cflag &= ~PARENB;
           options.c_cflag &= ~CSTOPB;
           options.c_cflag &= ~CSIZE;
           options.c_cflag |= CS8;
           // Disable hardware flow control.
           options.c_cflag &= ~CRTSCTS;
    
           // Choose Raw Input Input characters are passed through exactly as
           // they are received, when they are received.
    	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    	// Disable software flow control.
    	options.c_iflag &= ~(IXON | IXOFF | IXANY);
    	// Choose Raw Output, all other option bits in  c_oflag are ignored.
    	options.c_oflag &= ~OPOST;
    
    	options.c_cc[VMIN] = 1;
    	options.c_cc[VTIME] = 50;
    
    	cfsetispeed(&options, baud);
    	cfsetospeed(&options, baud);
    	tcsetattr(fd, TCSANOW, &options);
    This is based on the information contained in Serial Programming Guide for POSIX Operating Systems

    Jim

  5. #5
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    You might get further with "r+" rather than "w+".
    fopen - C++ Reference

    But then again, I would follow jimblumberg and use open/close etc.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  6. #6
    Registered User
    Join Date
    Oct 2010
    Posts
    9
    I read the man page on fcntl and I am not sure what "fcntl(fd, F_SETFL,0);" does.

    Secondly, the device I am connected to does use hardware flow control in case my software gets behind in reading from the device, it can pause and wait for me. Do you see any issue with me setting the CRTSCTS? I certainly do not want software flow control because it will corrupt my communications with the device I am connected with.

    Thanks for your help,
    Robert

  7. #7
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    From the link given above Serial Programming Guide for POSIX Operating Systems

    Reading Data from the Port

    Reading data from a port is a little trickier. When you operate the port in raw data mode, each read(2) system call will return the number of characters that are actually available in the serial input buffers. If no characters are available, the call will block (wait) until characters come in, an interval timer expires, or an error occurs. The read function can be made to return immediately by doing the following:

    fcntl(fd, F_SETFL, FNDELAY);

    The FNDELAY option causes the read function to return 0 if no characters are available on the port. To restore normal (blocking) behavior, call fcntl() without the FNDELAY option:

    fcntl(fd, F_SETFL, 0);

    This is also used after opening a serial port with the O_NDELAY option.
    Jim

  8. #8
    Registered User
    Join Date
    Oct 2010
    Posts
    9
    Is there a routine that would clear the input buffer (i.e. read everything off and throw it away)? I want to clear the buffer, than I want my next call to read to block indefinitely until data is present to be read. Was there a built in routine in the library, or do I need to write one?

    Thanks,
    Robert

  9. #9
    Registered User
    Join Date
    Oct 2010
    Posts
    9
    One other question. After doing a write, does the data go out immediately or is it buffered in linux? Normally I would use a fflush when I want to make sure data is sent immediately. Is there an equivalent when using open and write (vs fopen and fputc)?

    Thanks,
    Robert

  10. #10
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    Is there a routine that would clear the input buffer (i.e. read everything off and throw it away)? I want to clear the buffer, than I want my next call to read to block indefinitely until data is present to be read. Was there a built in routine in the library, or do I need to write one?
    I normally just read until read tells me that there is no more in the buffer.

    One other question. After doing a write, does the data go out immediately or is it buffered in linux? Normally I would use a fflush when I want to make sure data is sent immediately. Is there an equivalent when using open and write (vs fopen and fputc)?
    I'm not really sure if write is buffered. But from write

    After a write() to a regular file has successfully returned:

    Any successful read() from each byte position in the file that was modified by that write shall return the data specified by the write() for that position until such byte positions are again modified.

    Any subsequent successful write() to the same byte position in the file shall overwrite that file da
    So I would think it is not.

    Jim

  11. #11
    Registered User
    Join Date
    Oct 2010
    Posts
    9
    Since read blocks, I can't read unless there is a way to look ahead to see if there are characters in the input buffer. I want to clear the buffer, then go into a blocking read.

    There is some kind of timing issue because I am doing a write which issues a command to the external device I am talking to and it doesn't appear that it is going out in a timely manor, so that is why I ask if the write is buffered by the OS.

    Thanks,
    Robert

  12. #12
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    How much data are you trying to send/rec at one time? Could you re-post your current code?

  13. #13
    Registered User
    Join Date
    Oct 2010
    Posts
    9
    The code is a couple of hundred lines long. But let me tell you what I am doing. I use an open command. Then I write six bytes to the serial port (the command is two bytes long and I send three different commands). One of the commands is a command for the external device to change it's baud rate. The default baud rate is 9600 and I need to run at 115.2K. Once I issue the command I sleep for 100ms and then I change my local port's baud rate to 115.2K.

    Sometimes it is successful and I am able to talk to the external device at 115.2K, but other times, the device remains at 9600 and I can not talk anymore. I want to make sure that the OS isn't holding on to my output. The external device tech support people say I only need a 25ms delay after changing baud rate to be able to continue to talk to it.

    I though it would be good to purge the input and make sure that the output is flushed (if it is buffered).

    If you still think I should post the code, I will. Like I said, it is a few hundred lines long.

    Thanks,
    Robert

  14. #14
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    Please supply the code for changing the baud of your PC.

  15. #15
    Registered User
    Join Date
    Oct 2010
    Posts
    9
    Here is the code:

    Code:
      rtxSpaFd = open (RTX_SPA_DEVICE, O_RDWR | O_NOCTTY | O_NDELAY );
      if (rtxSpaFd == -1)
      {
        cout << "Can't open device " << RTX_SPA_DEVICE << ", error = " << _sys_errlist[errno] << endl;
        exit(0);
      }
    
      cout << "Device " << RTX_SPA_DEVICE << " successfully opened using File Descriptor #" << rtxSpaFd << endl;
    
      /* Reset the RTX SPA device by sending a break to it */
      fcntl (rtxSpaFd, F_SETFL, 0);
      tcsendbreak (rtxSpaFd, 0);
      usleep (100000);
    
      /* Configure RS-232 port for 9600 N81 Raw terminal */
      tcgetattr (rtxSpaFd, &serialPortOptions);
    
      cfmakeraw (&serialPortOptions);
      serialPortOptions.c_cflag &= ~PARENB;    /* Set to N81 */
      serialPortOptions.c_cflag &= ~CSTOPB;
      serialPortOptions.c_cflag &= ~CSIZE;
      serialPortOptions.c_cflag &= CS8;
      serialPortOptions.c_cflag &= ~CRTSCTS;    /* Disable hardware flow control */
      serialPortOptions.c_cc[VMIN] = 1;
      serialPortOptions.c_cc[VTIME] = 50;
    
      cfsetspeed (&serialPortOptions, B9600);  /* Start at 9600 */
    
      /* Store updated Terminal options */
      tcsetattr (rtxSpaFd, TCSANOW, &serialPortOptions);
    
      /* Flush all input data */
      tcflush (rtxSpaFd, TCIFLUSH);
    
      PutSerialChar (rtxSpaFd, (char)RTX_115K_BAUD);
      PutSerialChar (rtxSpaFd, (char)RTX_115K_BAUD);
    
      tcdrain(rtxSpaFd);
      usleep (100000);      /* Wait for RTX SPA baud rate change to take effect */
    
      /* Change local RS-232 port to 115.2K to match RTX SPA device */
      tcgetattr (rtxSpaFd, &serialPortOptions);
      cfsetspeed (&serialPortOptions, B115200);  /* Start at 115.2K */
      tcsetattr (rtxSpaFd, TCSANOW, &serialPortOptions);
    
        while (1) {
          /* get a byte from the serial device */
          spaRcvData = (unsigned char)GetSerialChar (rtxSpaFd);
               /* Loops here reading 5 bytes then writing a response */
    The GetSerialChar and PutSerialChar are below:

    Code:
    char
    Reader::GetSerialChar (int fileDescriptor) {
        char rcvData;
        int  result;
    
        result = read (fileDescriptor, &rcvData, 1);   /* Read one character */
        if (result < 0)
        {
            cout << "I/O error reading RS-232 device.  Error = " << _sys_errlist[errno] << endl;
            exit (0);
        }
        else if (result == 0)
            cout << "Warning, zero bytes read from RS-232 device" << endl;
    
        return (rcvData);
    }
    
    
    
    void
    Reader::PutSerialChar (int fileDescriptor, char outData) {
        int result;
    
        result = write (fileDescriptor, &outData, 1);
        if (result < 0)
        {
            cout << "I/O error writing RS-232 device.  Error = " << _sys_errlist[errno] << endl;
            exit (0);
        }
        else if (result == 0)
            cout << "Warning, zero bytes written to RS-232 device" << endl;
    
        return;

    After everything is running, the external device remains at 9600 baud. From the command line I can issue a stty -F /dev/ttyUSB0 9600 which brings my serial port back to 9600 and I start receiving messages. I don't think my write to the external device is getting there.

    Thanks,
    Robert

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Serial port read/write problem
    By ojaro in forum C Programming
    Replies: 5
    Last Post: 06-25-2010, 08:26 AM
  2. Send image (.jpg) file over serial port
    By aSpareName in forum Networking/Device Communication
    Replies: 2
    Last Post: 06-30-2009, 09:35 AM
  3. serial port to poll on request
    By infineonintern in forum C++ Programming
    Replies: 2
    Last Post: 06-11-2009, 06:52 AM
  4. Serial Port - Problem with binary frames
    By estratos in forum Linux Programming
    Replies: 2
    Last Post: 01-18-2006, 06:22 AM
  5. Serial port read..can someone tell me..
    By Unregistered in forum C Programming
    Replies: 3
    Last Post: 06-27-2002, 08:21 AM

Tags for this Thread