简体   繁体   中英

Only able to read one byte via serial

I've got a C/Python setup on my machine, I'm doing some testing with serial communications and for some reason I'm never reading more than 1 byte back.

My set up: I have a windows 7 machine, running OpenSUSE in a virtual box. I have 2 USB-RS232 converters and an adaptor between them (so it's a loop from one usb port to the other).

On the Windows side I was able to get them to communicate with each other via Python-to-Python, and C-to-Python. Once I use the Linux VM, I can get data from the C (Linux) to the Python (Windows), but when I do it the other way around I only get 1 byte back. I'm thinking it's something wrong with how I open the file or execute the read on the Linux C code, but I'm not sure what could be the issue.

Python Code (using PySerial):

>>> import serial
>>> ser = serial.Serial(3)
>>> ser
Serial<id=0x2491780, open=True>(port='COM4', baudrate=9600, bytesize=8, 
parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
>>> ser.read(5)
'Hello'
>>> ser.write("hi you")
6L

The C code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

int open_port()
{
    int fd;
    fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
    if(fd < 0)
      perror("open_port: Unable to open /dev/ttyUSB0 - ");
    else
      fcntl(fd, F_SETFL, 0);
    return fd;
}

int swrite(int fd, char * str)
{
    int n;
    n = write(fd, str, strlen(str));
    if (n<0)
        printf("write() of %d bytes failed\n", strlen(str));
    return n;
}

int main()
{
    int fd, databytes;
    char buf[100] = {0};
    struct termios options;

    fd = open_port();

    //Set the baud rate to 9600 to match
    tcgetattr(fd, &options);
    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);
    tcsetattr(fd, TCSANOW, &options);
    tcgetattr(fd, &options);

    databytes = swrite(fd, "Hello");
    if(databytes > 0)
      printf("Wrote %d bytes\n", databytes);

    databytes = read(fd, buf, 100);
    if(databytes < 0)
      printf("Error! No bytes read\n");
    else
      printf("We read %d bytes, message: %s\n", databytes, buf);

    close(fd);

    return 0;
}

And I'm getting back:

mike@linux-4puc:~> gcc serial_com.c
mike@linux-4puc:~> ./a.out 
Wrote 5 bytes
We read 1 bytes, message: h

So the Linux->Windows write is working, python is showing the correct "Hello" string, but for some reason I'm only getting one byte back on the Windows->Linux side of things.

Anyone see anything wrong?

EDIT:
Based on the feedback that I've gotten, I've tried two tweaks to the code. Sounds like I can't guarantee that all the data will be there, so I've tried:

1) a sleep

    if(databytes > 0)
      printf("Wrote %d bytes\n", databytes);
    sleep(15);                 // Hack one to get the data there in time, worked
    databytes = read(fd, buf, 100);

2) a while loop

while(1){  // Hack two to catch the data that wasn't read the first time. Failed
           // this only saw 'h' like before then sat waiting on the read()
  databytes = read(fd, buf, 100);
  if(databytes < 0)
    printf("Error! No bytes read\n");
  else
    printf("We read %d bytes, message: %s\n", databytes, buf);
}

Seems the loop doesn't work, so does the data not read get trashed?? /EDIT

From the read(2) manual ;

On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal)

In other words, since you read from the socket right after writing and your computer is quite a lot faster than the serial port, there is most likely only a single character available to read and read(2) returns only that character.

The man page for read says

...attempts to read up to count bytes...

Your code looks like it assumes the full buffer will always be returned by a single read ; its valid for the data to be returned over several calls.

It'd also be good practice to check for read returning -1 with errno == EINTR and retry after this (use TEMP_FAILURE_RETRY if running on a GNU system). read may return a transient error if it is interrupted by a signal.

As others have answered, the C read() function is fulfilling its contract by only returning a single byte.

The C read() and Python read() functions are quite different.

PySerial says, about read(),

Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read.

While the C API makes no such guarantee. It will return any characters which are available in the buffer, (even 0, if there's nothing there yet).

If you want the C side to behave like the Python side, you'll need to use a different function. Something like this:

int read_exact_chars(int fildes, char *buf, size_t nbyte)
{
    ssize_t chars_read;
    int chars_left = nbyte;
    while (chars_left) {
        chars_read = read(fildes, buf, chars_left)
        if (chars_read == -1) {
            /* An error occurred; bail out early. The caller will see that
               we read fewer bytes than requested, and can check errno
             */
            break;
        } else {
            buf += chars_read;
            chars_left -= chars_read;
        }
    }
    /* return the actual number of characters read */
    return nbyte - chars_left;
}

Got some good answers and incite on this (+1's all around!), while all of the answers lead to correct conclusions (about read not getting the data), none of the input actually "fixed" the problem I was having.

Here's how I got this to work finally:

The settings in the termios structure were killing me. While I was setting some of the flags, I wasn't setting all of them. So this:

tcgetattr(fd, &options);
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
tcsetattr(fd, TCSANOW, &options); 

Changed to this:

tcgetattr(fd, &options);
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
options.c_iflag = 0; // Disable Input flags
options.c_lflag = 0; // Disable Local mode flags
options.c_oflag = 0; // Disable Output flags
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8; //Data bits per character (8)
options.c_cc[VMIN] = 50;  // Wait for 50 characters or
options.c_cc[VTIME] = 50; // Wait for 5 seconds
options.c_cflag |= (CLOCAL | CREAD | HUPCL);   // Ignore modem status lines, 
                                               // enable receiver, 
                                               // and hang up on last close
options.c_cflag &= ~(PARENB | PARODD); //Clearing even and odd parity
options.c_cflag &= ~CSTOPB;            //Clear double stop bits
tcsetattr(fd, TCSANOW, &options);

With these changes now I get the data in my C Linux code that I write from Python.

I used these two sources for a lot of "facts" about what the options are for the termios structure:

  1. flag description
  2. Quick Overview

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM