Search code examples
serial-portlinux-device-driverembedded-linuxuartzynq

Linux UART communication, first byte drop


I am trying uart communication between PC as sender ( Ubuntu 20.04 ) and embedded device as receiver ( using petalinux ( analogdevicesinc/plutosdr-fw)) via usb to ttl (3.3V) converter. On embedded device uart driver is "xilinx_uartps" .For Linux uart communication I am referring to simple code at https://blog.mbedded.ninja/programming/operating-systems/linux/linux-serial-ports-using-c-cpp/. The problem is that i cant handle first byte , when i send {'A','B','C'} i receive {'B','C'}. But if i send it with null termination like {'\0','A','B','C'} its all fine at the receiver part. I decoded communication with logic analyzer and there is no problem at sending bytes from PC, its all about receiving them with embedded device. Is there any suggestions or solutions? Thanks for supports.

The Sender Part:

int main() {
  // Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
  int serial_port = open("/dev/ttyUSB3", O_RDWR);

  // Create new termios struct, we call it 'tty' for convention
  struct termios tty;

  // Read in existing settings, and handle any error
  if(tcgetattr(serial_port, &tty) != 0) {
      printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
      return 1;
  }
  
    tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
    tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
    tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size 
    tty.c_cflag |= CS8; // 8 bits per byte (most common)
    tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
    tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO; // Disable echo
    tty.c_lflag &= ~ECHOE; // Disable erasure
    tty.c_lflag &= ~ECHONL; // Disable new-line echo
    tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

    tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
    tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
    // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)
    // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)

    tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
    tty.c_cc[VMIN] = 0;
    cfsetispeed(&tty, B115200);
    cfsetospeed(&tty, B115200);


  // Save tty settings, also checking for error
  if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
      printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
      return 1;
  }
  while(1){
    int oneTime = 0;
    scanf("%d", &oneTime); // send message for every input "1"
    unsigned char msg[] = { '\0','A', 'B', 'C', 'D', 'E' };
    if(oneTime == 1)
      printf("sending");
      write(serial_port, msg, sizeof(msg));
      oneTime = 0;
  }  
  close(serial_port);
  return 0; 
};

The Receiver Part:

int main(){

    // Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
    int serial_port = open("/dev/ttyPS0", O_RDWR);

    // Create new termios struct, we call it 'tty' for convention
    struct termios tty;

    // Read in existing settings, and handle any error
    if(tcgetattr(serial_port, &tty) != 0) {
        printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
        return 1;
    }

    tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
    tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
    tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size 
    tty.c_cflag |= CS8; // 8 bits per byte (most common)
    tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
    tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO; // Disable echo
    tty.c_lflag &= ~ECHOE; // Disable erasure
    tty.c_lflag &= ~ECHONL; // Disable new-line echo
    tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
    tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

    tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
    tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
    // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)
    // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)

    tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
    tty.c_cc[VMIN] = 0;
    cfsetispeed(&tty, B115200);
    cfsetospeed(&tty, B115200);

    // Save tty settings, also checking for error
    if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
        printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
        return 1;
    }


    // Allocate memory for read buffer, set size according to your needs
    char read_buf [256];

    memset(&read_buf, '\0', sizeof(read_buf));
    int num_bytes;

    while(1){

        num_bytes = read(serial_port, &read_buf, sizeof(read_buf));

        if (num_bytes <= 0) {
            printf("Error reading: %s", strerror(errno));
            break;
        }
        printf("Read %i bytes. Received message: %s", num_bytes, read_buf);    
        
    }

  close(serial_port);

return 0;
}

Solution

  • I am trying uart communication between PC as sender ( Ubuntu 20.04 ) and embedded device as receiver ...

    Your Linux application programs have no direct access to the hardware (i.e. the UART), but instead access a serial terminal using the termios API. See Linux serial drivers to grasp the layers of code involved.

    The Sender Part:
    ...
    The Receiver Part:
    ...

    The code you have posted has numerous bugs which makes replication of the problem you report difficult.


    The Receiver terminates on a valid read timeout

    When executing your Receiver program from the shell, the read() request is likely to simply timeout after one second, (incorrectly) reports an error, and then terminates.

    $ ./a.out
    Error reading: Success$
    

    Unless you have initiated transmission of the serial message within that 1 second of open() and read() time, that Receiver program is unlikely to acquire any characters at all. Are you relying on some two-handed synchronization tricks to get these two programs to pass data?

    The simple solution to this bug is to stop treating a timeout with no data as an error. That is, a return code of 0 from read() is not an error, but is a valid return condition.

             num_bytes = read(serial_port, &read_buf, sizeof(read_buf));
    
    -        if (num_bytes <= 0) {
    +        if (num_bytes < 0) {
                 printf("Error reading: %s", strerror(errno));
                 break;
             }
    

    read() does not return a string

    The read() syscall simply returns an array of bytes, rather than a null-terminated string. However your printf() does assume the buffer contents are a sting.
    There's a memset() to clear the buffer, but that happens only once before the while loop is entered.
    The proper fix is the use num_bytes to append a null byte to properly terminate the text. Note that the read request count should be reduced by one to insure that there's a spare byte in the array post-read.

    -        num_bytes = read(serial_port, &read_buf, sizeof(read_buf));
    +        num_bytes = read(serial_port, &read_buf, sizeof(read_buf) - 1);
    
             ...
    +        read_buf[num_bytes] = 0;
             printf("Read %i bytes. Received message: %s\n", num_bytes, read_buf);    
    

    But if i send it with null termination like {'\0','A','B','C'} its all fine at the receiver part.

    Starting a text message with a string terminator make no sense at all.
    On a successful read by your Receiver program, I would see the bizarre display of:

    ...  
    Read 6 bytes. Received message: 
    ...
    

    The string terminator at the beginning of the buffer will inhibit the printing of all the received characters!
    Why not use a punctuation mark such as '?' instead of an unprintable or a control character?

    ...
    Read 6 bytes. Received message: ?ABCDE
    ...
    

    Noncanonical read will not reliably return complete "messages"

    Whatever you think constitutes a serial message or packet or datagram, a noncanonical read() is never assured of returning a complete "message". The VMIN and VTIME termios parameters can be adjusted match the expected characteristics of incoming messages; see Linux Blocking vs. non Blocking Serial Read. But the receiving program must always be able to accommodate "short" or partial reads. See this answer for a buffering scheme.


    Unable to reliably replicate the reported problem

    After making the code changes described above plus making the printf()s more readable, I am unable to reliably replicate the reported problem.
    Some sample results of the tweaked Receiver program:

    Read 0 bytes. Received message: 
    Read 6 bytes. Received message: ?ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 1 bytes. Received message: ?
    Read 3 bytes. Received message: ABC
    Read 2 bytes. Received message: DE
    Read 1 bytes. Received message: ?
    Read 5 bytes. Received message: ABCDE
    Read 1 bytes. Received message: ?
    Read 5 bytes. Received message: ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 1 bytes. Received message: ?
    Read 5 bytes. Received message: ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 1 bytes. Received message: ?
    Read 5 bytes. Received message: ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 1 bytes. Received message: ?
    Read 5 bytes. Received message: ABCDE
    Read 6 bytes. Received message: ?ABCDE
    Read 1 bytes. Received message: ?
    Read 5 bytes. Received message: ABCDE
    

    read() does seem to have an occasional inclination to return a short read of just the first character of the message, but it is never lost or "dropped". And it can also occasionally return the complete message in one buffer.
    There is no consistent "failure" as reported when using my setup.