0

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;
}
sawdust
  • 16,103
  • 3
  • 40
  • 50
  • It can be anywhere from setup to script you are sending. If providing some example codes it would help us a lot. Also I saw setting in de docs in section 8.2 perhaps it would quick fix your issue. but this wouldn't make It clearer for us – Jayr Aug 16 '22 at 07:45
  • Thanks for comment, i added the code blocks. As you said, i noticed the 8.2 part but it handles the special bytes if setted, i want all data i send. @Jayr – tripleclick Aug 16 '22 at 08:48
  • I used oscilloscope and scoped the lines. When i decoded the data, the result is this sending part is all okey. I can correct send the packet to the embedded device, but at embedded side there is first byte loss. I checked this by using cat command on /dev/ttyPS0 @sawdust – tripleclick Aug 16 '22 at 08:52
  • In your receiver program, `if (num_bytes <= 0)`, a return code of zero is not an error, but a valid return. There's also a subtle bug. The **read()** syscall does not return a string, but your **printf()** does treat the buffer contents as a sting. There's a **memset()** to clear the buffer, but that happens only once. Proper fix is the use `num_bytes` to append the string terminator, i.e. `read_buf[num_bytes] = 0;` after a good read. BTW `read_buf` is already an address (of an array), so using `&read_buf` is redundant. – sawdust Aug 16 '22 at 09:19
  • '*there is first byte loss*" -- First byte of every message? First byte after starting programs? What order do you start these programs? Try changing/using `tty.c_cc[VMIN] = 1;` in receive program – sawdust Aug 16 '22 at 09:29
  • I changed the receive part as mentioned. Printed buffer as hex in for loop, VMIN =1 and . read_buf[num_bytes] = 0; after a good read. When i send {'A','B','C','D','E'} the output was 0x42,0x43,0x44,0x45. The A is not received. And this bug occurs at every message @sawdust – tripleclick Aug 16 '22 at 10:02
  • The num_bytes returns 4 every time, but i send 5 bytes – tripleclick Aug 16 '22 at 10:03

1 Answers1

0

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.

sawdust
  • 16,103
  • 3
  • 40
  • 50
  • thank you @sawdust for your support, i just realized that the problem was virtual terminal. Getty was working as deamon, the problem all solved when i killed getty. – tripleclick Aug 18 '22 at 10:38