0

I'm working with a serial device RFID reader (caenrfid lepton7). It has a mode where it can send data forever each time it sees a new RFID tag. The problem with the nature of the serial line is that if my program crashes while reading a frame the device will continue to send data until I stop it using either a physical switch (it has a pin dedicated for that) or a push button on the device. For the moment, during my tests I use the physical user button. Then, because it has sent more data that I haven't read the Linux kernel enqueue the incoming buffer into the tty line but since it may contain a partial frame I need to discard that because it's not recoverable. To my understanding the way to go is to use tcflush. So after opening the device this is what I try:

int
etrp_open(const char *path)
{
    assert(self);
    assert(path);

    int fd;
    struct termios tty;

    if ((fd = open(path, O_RDWR | O_NOCTTY)) < 0)
        return -1;
    if (tcgetattr(fd, &tty) < 0) {
        close(fd);
        return -1;
    }

    tty.c_cflag &= ~(PARENB | CSIZE);
    tty.c_cflag |=   CLOCAL | CS8 | CREAD;
    tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG);

    tty.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
    tty.c_oflag &= ~(OPOST);

    if (cfsetispeed(&tty, B921600) < 0 ||
        cfsetospeed(&tty, B921600) < 0 ||
        tcsetattr(fd, TCSAFLUSH, &tty) < 0) {
        close(fd);
        return -1;
    }

    if (tcflush(fd, TCIOFLUSH) < 0) {
        close(fd);
        return -1;
    }

    return 0;
}

But after the tcflush call, even though the device stopped sending data if I call read on fd it will actually read! Why is that?

    [... as before ...]

    if (tcflush(fd, TCIOFLUSH) < 0) {
        close(fd);
        return -1;
    }

    char buf[1024];
    ssize_t nr;
    nr = read(fd, buf, sizeof (buf));
    printf("read = %zd\n", nr);

When running the application and after have started the non-stop arrival of data, closing my app, waiting a few seconds, pressing the kill switch. Then the read actually reads:

./scanner 
read = 128

I have found in another thread that I need to call usleep before tcflush but I don't get why, I've tried and it seems to work most of the time but I don't really like having to do some sleep code to re-initialize correctly a device.

My questions:

  1. Why is that usleep call necessary?
  2. What is usually the proper way to completely discard not read data AND incoming data from a partial frame? (imagine a device that will continue to send its data even after a reset).
markand
  • 495
  • 1
  • 4
  • 16
  • Your userspace program is many layers and buffers removed from the actual HW connection. See [Linux serial drivers](https://www.linux.it/~rubini/docs/serial/serial.html). Is USB involved (and its layers & buffering have to be considered)? BTW your termios configuration is incomplete. For noncanonical mode, VMIN and VTIME need to be defined. Note a UART *frames* just characters/bytes, not messages or packets or datagrams. And in noncanonical mode there's no concept of "*frame*", message or otherwise. – sawdust Jul 12 '22 at 18:58
  • the code needs to save the original I/O configuration before changing it. Then when done with the I/O the code should restore the original configuration – user3629249 Jul 14 '22 at 09:16
  • you need to determine that actual format of a frame of data. then parse the data stream into frames of data. then your parsing function can find the start/end of a data frame and discard any partial frame – user3629249 Jul 14 '22 at 09:33
  • @sawdust why are VMIN/VTIME required? It's 0 by default I think (or whatever tcgetaddr retrieve). Yes the device is USB based one and generates either /dev/ttyACM0 or /dev/ttyUSB0. – markand Jul 18 '22 at 06:47
  • "*why are VMIN/VTIME required?*" -- See https://stackoverflow.com/questions/25996171/linux-blocking-vs-non-blocking-serial-read/26006680#26006680 If you leave these values to chance or use 0, you may get "short" reads, incur more overhead, and reduce system efficiency. I have not had a chance to test for your issue, but my recollection is that (modern) Linux kernels discard the data while the serial terminal is not open. So it would be a rather short time interval between the **open()** and **tcflush()**. But given the fast baudrate, you're probably using a CDC ACM connection. – sawdust Jul 18 '22 at 07:27
  • Just a guess... Have you tried using `cfmakeraw()` instead of messing with the flags directly? What I do in my case is: `tcgetattr()`, `cfmakeraw()`, `cfsetspeed()`, set `VMIN/VTIME`, apply with `tcsetattr()` and finally call `tcflush(TCIOFLUSH)`. And the queue is flushed properly. You also didn't mention what system you are on. Because in my case, `tcflush()` is needed only on macOS and only with a certain USB-TTL adapter, but has no effect on other systems (tested on Linux/BSD/Cygwin) - the driver seems to be flushing the queue. – neoxic Aug 31 '22 at 04:43

0 Answers0