无法写入串行设备,但可以读取

问题描述

我正在使用非阻塞IO,在这里我只是尝试写入和读取串行端口。读取串口可以按预期工作,而写入则不能。

这就是我设置串行线的方式。如您所见,它设置为非阻塞和规范的。某些标志可能是多余的,但它们大多来自此处的示例:Serial setup example

tSerial::tSerial(const char *serialPort,bool echo)
{
    // Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
    m_serialPort = open(serialPort,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(m_serialPort,&tty) != 0)
    {
        spdlog::error("[tSerial] error {} from tcgetattr: {}",errno,strerror(errno));
        throw std::runtime_error("Failed to get existing serial settings");
    }

    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_cflag |= ICANON;
    if (!echo)
    {
        tty.c_lflag &= ~ECHO; // disable echo
    }
    else
    {
        tty.c_lflag |= ECHO; // Enable 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

    // Set in/out baud rate to be 115200
    cfsetispeed(&tty,B115200);
    cfsetospeed(&tty,B115200);

    // Save tty settings,also checking for error
    if (tcsetattr(m_serialPort,TCSANow,&tty) != 0)
    {
        spdlog::error("[tSerial] error {} from tcsetattr: {}",strerror(errno));
        throw std::runtime_error("Failed to set new serial settings");
    }

    // Set non-blocking
    int flags;
    if ((flags = fcntl(m_serialPort,F_GETFL,0)) == -1)
    {
        flags = 0;
    }
    fcntl(m_serialPort,F_SETFL,flags | O_NONBLOCK);
}

这就是我尝试写的方式。我正在使用select等待IO资源变得可用,因为尝试直接写入它会产生错误Resource temporarily unavailable。但是即使等待20秒,该设备仍然不可用。

void tSerial::writeSerial(std::string message)
{
    int n;
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(m_serialPort,&rfds);
    spdlog::debug("[tSerial] writing command to serial {}",message);
    struct timeval tv;
    tv.tv_sec = 20;
    tv.tv_usec = 0;
    int retval = select(1,NULL,&rfds,&tv);
    if (retval == -1)
    {
        spdlog::error("[tSerial] select error");
    }
    else if (retval)
    {

        n = write(m_serialPort,message.c_str(),message.length());
        tcflush(m_serialPort,TCIOFLUSH);
        if (n < 0)
        {
            spdlog::error("[tSerial] error writing: {}",strerror(errno));
        }
    }
    else
    {

        spdlog::error("[tSerial] Resource unavailable after 20 seconds wait");
    }
}

解决方法

我正在使用select等待IO资源可用...但是即使等待20秒,设备仍然不可用。

您的程序运行不正常,因为您没有正确设置 select()调用:

int retval = select(1,NULL,&rfds,&tv);

man 页上,第一个参数“ 应设置为三组中加号最多的文件描述符中的任意一个,加1。
由于您传递的是值1,因此 select()可以检查的唯一文件描述符是stdin(只读,永远不会准备输出)永远不要打开的串行终端(文件描述符)。
相反,系统调用必须是

select(m_serialPort + 1,...);

您的代码还有其他问题。

(1)使用非阻塞模式值得怀疑。
似乎您喜欢向程序中添加额外的代码以等待,而不是让操作系统为您完成。如果您的程序不能有效地利用其时间片,那么最好由操作系统来管理系统资源。

(2)在 write()之后使用 tcflush()是荒谬和错误的!
您可能打算使用 tcdrain()
研究 man 页。

(3)复制和修改代码时,您至少引入了一个错误。
ICANONc_lflag成员中,而不在c_cflag中。

(4)良好的编码习惯是使用有意义的变量名。
将用于读参数的变量名重用为写参数是草率且令人困惑的:

fd_set rfds;  
...
int retval = select(...,&tv);

您的程序应该使用rfds之类的东西作为第三个参数,而不是wfds


...尝试直接写给它,将显示错误Resource temporarily unavailable

使用 select()时出现的问题很容易解释。
对于上述问题,您需要提供一个最小的,可重复的示例,即完整的程序。