1

I'm using a Raspberry Pi Zero in device mode and a Raspberry Pi B in host mode. I'm connecting the two with a USB cable. My goal right now is just to send simple, arbitrary data back and forth between the two Pi's.

The problem is whichever Pi writes to the serial port first ends up reading what it wrote. The program I've written has the device send d\n and the host send h\n. So, if the device writes first, the the host reads correctly the d\n, then writes h\n to the serial port. But the device ends up reading d\n! The problem persists if I switch it so that the host writes first.

I've tried adding various tcflush calls to the program after writing but before reading, but it doesn't work. I've also tried sleeping for various amounts of time. I've read waiting 100 microseconds for each character written and I've slept for several seconds.

My setup requires me to not have a constant connection between both Pi's at the same time because of the Pi Zero's single data-capable usb port. So, to test, I'm actually plugging in a keyboard and running the program, then plugging in the proper cable to transfer data. I can transfer data, but not after writing because the program simply reads back what it wrote.

I'm starting to think I've fallen into a noob trap that I can't fathom. Here is the code I'm using:

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

/*
 * gcc -o device_rw -DDEVICE serial_rw.c
 * gcc -o host_rw serial_rw.c
 */

#define SERIAL_DEVICE "/dev/ttyGS0"
#define SERIAL_HOST   "/dev/ttyACM0"

#ifdef DEVICE
#define _TTY SERIAL_DEVICE
#else
#define _TTY SERIAL_HOST
#endif

int
set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */

    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */
    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */

    tty.c_iflag |= IGNPAR | IGNCR;
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag |= ICANON;
    tty.c_iflag &= ~OPOST;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }

    return 0;
}

void
write_serial (int fd, const char *buf, int len)
{
    printf("WRITE: %s\n", buf);
    write(fd, buf, len);
}

void
read_serial (int fd, char *buf, int len)
{
    ssize_t nread = read(fd, buf, len);
    if (nread > 0 && nread <= len) {
      buf[nread] = 0;
      printf(" READ: %s\n", buf);
    }
}

int 
main (int argc, char **argv)
{
    char buf[80];
    int fd = open(_TTY, O_RDWR | O_NOCTTY);

    if (fd < 0) {
      fprintf(stderr, "Can't open %s: %s\n", _TTY, strerror(errno));
      goto exit;
    }

    if (set_interface_attribs(fd, B115200) < 0) {
      goto exit;
    }

#ifdef DEVICE
    printf("device: %s\n", _TTY);
    write_serial(fd, "d\n", 2);
    usleep((2 + 25) * 100);
    read_serial(fd, buf, 2);
#else
    printf("host: %s\n", _TTY);
    read_serial(fd, buf, 2);
    //usleep((2 + 25) * 100);
    write_serial(fd, "h\n", 2);
#endif

    close(fd);
exit:
    return 0;
}
Leroy
  • 237
  • 2
  • 12

2 Answers2

4

Beside not disabling the ECHO attributes (as @MarkPlotnick commented), you have two misapplied assignments:

tty.c_iflag |= ICANON;

ICANON belongs to the lflag member, and

tty.c_iflag &= ~OPOST;

OPOST belongs to the oflag member.
Considering these errors, did you properly apply @MarkPlotnick's suggestion?

See Working with linux serial port in C, Not able to get full data for a working canonical setup.

cfsetospeed(&tty, (speed_t)speed);
cfsetispeed(&tty, (speed_t)speed);

tty.c_cflag |= CLOCAL | CREAD;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;         /* 8-bit characters */
tty.c_cflag &= ~PARENB;     /* no parity bit */
tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

tty.c_lflag |= ICANON | ISIG;  /* canonical input */
tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);

tty.c_iflag &= ~INPCK;
tty.c_iflag |= IGNCR;
tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
tty.c_iflag &= ~(IXON | IXOFF | IXANY);   /* no SW flowcontrol */

tty.c_oflag &= ~OPOST;

Note that it is also possible to have echo enabled on the far end.
To determine if the echo is generated locally or from the far end, simply disconnect the remote device (assuming you're using UARTs and/or USB-to-serial adapters), and transmit.
If you still get echo, then it's generated locally, which is controlled by the termios ECHO attributes.
If you no longer get an echo, then it's the remote unit that is repeating its input back to the sender.


BTW Your program as posted does not compile cleanly.
It's missing #include <string.h>
The termios initialization routine that you copied (but then incorrectly modified) has proper checking of return values, but your read and write routines do not check for errors.

Community
  • 1
  • 1
sawdust
  • 16,103
  • 3
  • 40
  • 50
0

I learned a lot from everyone's answers and am grateful for them because I will need them as I move on with this project. For this particular case, however, the issue ended up being permissions. Yep, file permissions on ttyGSO.

I completely expected a permission denied error from open and never got one, so I never considered the possibility. Especially since I opened the files in RDWR mode, I could write to the serial file, and it appeared I was reading data (although the same data) it never dawned on me that perhaps I didn't have read permissions.

Leroy
  • 237
  • 2
  • 12