C++ 串口读取问题:ioctl(FIONREAD) 是否设置了错误的值?

C++ Serial port reading issue: does ioctl(FIONREAD) set a wrong value?

我遇到了一个非常奇怪的问题,我无法解决。我想使用 c++ 在 Mac Os X 上读取(仅读取)微控制器通过 USB 作为串口(FTDI)收集和发送的数据。一个完整数据序列的大小始终正好是 10 个字节。但是我使用以下代码读取数据:

导入的库:

#include <iostream>
#include <fstream>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>

代码:

void init(){
    serial = open(port.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); // 0_RDONLY ?
    struct termios options;
    //set opt to 115200-8n1
    cfsetspeed(&options, B115200);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    tcsetattr(serial, TCSANOW, &options);
    if (serial < 0){
        //Error
    }else{
        //run loop
    }
}

void serial_loop(){
    long bytes_read;
    int bytes_available;
    unsigned char msg[10];
    while(1){
        do{
            usleep(1000);
            ioctl(serial, FIONREAD, &bytes_available);

        }while(bytes_available < 10); //wait for the sequence to complete

        bytes_read = read(serial, msg, 10);

        //do some parsing here
    }
}

此代码在几天前有效,但现在已无效。根据终端 -> 屏幕命令,数据可以完美地到达计算机。我检查了仍然正确的端口文件名,并且端口也已成功打开。 我将我的问题缩小到 ioctl-command FIONREAD,它没有将正确的数字写入 bytes_available-var(不再)。 它确实有效,而且我相信,我没有更改代码中的任何内容。

您是否发现任何可能导致此问题的问题? 我的代码中是否有任何危险的段落? 谢谢你的帮助,我真的被困在这里了...

编辑: 多亏了反馈,我才能够再次得到它运行。这是当前代码:

int serial;
void init(){
    serial = open(port.c_str(), O_RDWR | O_NOCTTY); //removed 0_NONBLOCK
    struct termios options;
    //set opt to 115200-8n1
    cfsetspeed(&options, B115200);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //Non-canonical
    options.c_cc[VMIN]     = 1; //block read until at least 1 byte was recieved
    options.c_lflag = 0;

    tcsetattr(serial, TCSANOW, &options);
    if (serial < 0){
        //Error
    }else{
        //run loop
    }
}

void serial_loop(){
    int datalength = 10;
    long bytes_read = 0;
    int bytes_in_msg = 0;
    unsigned char buf[datalength];
    unsigned char msg[datalength];
    do{
        bytes_read = read(serial, buf, datalength-bytes_in_msg);
        usleep(1000);
        if (bytes_read>0){
            memcpy(&msg[bytes_in_msg], &buf, bytes_read);
        }
        bytes_in_msg += bytes_read;
    }while(bytes_in_msg < datalength);

    //do some parsing here
    }
}

这是有效的,但还有什么可能有问题吗?

感谢您的支持!

您的代码已损坏。没有协议可以允许 reader 等待写入者和写入者等待 reader。如果这样做,可能会导致死锁。

您的 do/while 循环拒绝读取任何数据,直到写入器写入所有 10 个。但是,串行端口允许写入器拒绝写入更多数据,直到 reader 读取它已经拥有的数据书面。因此它也不能允许 reader 拒绝读取更多数据,直到作者写入更多数据。

您不能使用FIONREAD等待写入更多字节,因为写入器可能也在等待您。相反,在字节可用时读取字节,从而确保您解除对编写器的阻塞。将它们累积在缓冲区中,当你有需要的数量时中断。

This code worked a few days ago but now it isn't anymore.

变化无常的程序行为通常表示初始化不当或不完整。
您的 termios 初始化仅配置波特率和字符帧,其他一切都留给机会。
参见 Setting Terminal Modes ProperlySerial Programming Guide for POSIX Operating Systems.


您修改后的代码仍未正确解决此问题。
代码从不通过调用 tcgetattr() 函数来初始化 termios 结构 options
有关示例代码,请参阅我对 How to open, read, and write from serial port in C?

的回答
options.c_lflag = 0;

这不是分配 termios 元素的正确方法。


options.c_cc[VMIN]     = 1;

非规范模式需要同时定义 VMIN 和 VTIME 条目。
您的代码使用 VTIME 所在位置存在的任何垃圾值。
参见 Linux Blocking vs. non Blocking Serial Read


FIONREAD which doesn't write the correct number to the bytes_available-var (anymore).

负面描述,即没有发生的事情,不如对发生的事情的描述有用或具体。
那么您要返回什么样的值?
为什么你认为它是“错误的”?

更重要的是,您为什么不检查每个系统调用的 return 值,尤其是您认为会给您带来问题的 ioctl()

很可能是 ioctl() 失败,没有更新您的 bytes_available 变量,并且 return 编辑了一个错误代码。
您的代码无条件地使用 returned 参数,而不是首先检查一个好的 return。


另一个批评您的代码的答案具有误导性。可以禁用流量控制。您的代码被破坏是因为它没有进行正确的初始化并且没有检查错误 returns,而不是因为 comm link 是 “死锁”.


在您修改后的代码中:

    bytes_read = read(serial, buf, datalength-bytes_in_msg);
    usleep(1000);

阻塞读取之后 and/or 之前的休眠或延迟是多余的。