【问题标题】:Linux serial read blocks minicomLinux 串行读取块 minicom
【发布时间】:2017-06-10 01:17:25
【问题描述】:

我正在尝试从 BeagleBone Black 上的串行端口 (/dev/ttyS4) 读取数据,但我认为(?)这通常适用于所有 Linux 设备。

目前,我可以设置minicom,波特率为9600,数据为8N1,以正确读取串口。但是,如果我尝试直接cat /dev/ttyS4,我的终端中不会显示任何内容。我的代码也这样做,并返回一个Resource temporarily unavailable 错误,我怀疑这是cat 命令发生的情况。

如果我运行stty -F /dev/ttyS4,我会得到以下输出(据我所知,这与我的minicom 设置一致):

speed 9600 baud; line = 0;
intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; start = <undef>; stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; flush = <undef>;
-brkint -imaxbel
-opost -onclr
-isig -iexten -echo -echoe -echok -echoctl -echoke

有趣的是,当我打开minicom 时,如果我启动我的程序,minicom 将停止打印任何内容,并且即使我停止我的程序也会保持这种状态。我需要再次打开串行设置(Ctrl-AP)并关闭它以使minicom 恢复工作(似乎没有任何改变)。

我的代码如下:

int main() {
    std::cout << "Starting..." << std::endl;

    std::cout << "Connecting..." << std::endl;
    int tty4 = open("/dev/ttyS4", O_RDWR | O_NOCTTY | O_NDELAY);
    if (tty4 < 0) {
        std::cout << "Error opening serial terminal." << std::endl;
    }

    std::cout << "Configuring..." << std::endl;
    struct termios oldtio, newtio;
    tcgetattr(tty4, &oldtio);   // save current serial port settings
    bzero(&newtio, sizeof(newtio)); // clear struct for new settings

    newtio.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
    newtio.c_iflag = IGNPAR | ICRNL;
    newtio.c_oflag = 0;
    newtio.c_lflag = ICANON;

    tcflush(tty4, TCIFLUSH);
    tcsetattr(tty4, TCSANOW, &newtio);

    std::cout << "Reading..." << std::endl;
    while (true) {
        uint8_t byte;
        int status = read(tty4, &byte, 1);
        if (status > 0) {
            std::cout << (char)byte;
        } else if (status == -1) {
            std::cout << "\tERROR: " << strerror(errno) << std::endl;
        }
    }

    tcsetattr(tty4, TCSANOW, &oldtio);
    close(tty4);
}

编辑:按照 Adafruit 的将 python 与 BeagleBone 结合使用的教程,我已经让串行端口正常工作(在 python 中)。在这一点上,我确定 做错了什么;问题是什么。我更喜欢使用 C++ 而不是 python,所以让它工作会很棒。

【问题讨论】:

  • 我看到了几个可能的问题,但您的问题是什么?
  • 我应该如何修改我的c++程序才能成功从串口读取?

标签: c++ linux serial-port termios


【解决方案1】:

您的程序以非阻塞模式打开串行终端。

   int tty4 = open("/dev/ttyS4", O_RDWR | O_NOCTTY | O_NDELAY);

非阻塞 I/O,尤其是读取操作,需要在程序中进行额外的特殊处理。 由于您忽略了此模式,并且您的程序无法正确处理此模式,这可能被认为是一个错误。

open() 调用中删除 O_NDELAY 选项,或插入 fcntl(tty4, F_SETFL, 0) 语句以恢复到阻塞模式。


我的代码也是这样做的,并返回一个Resource temporarily unavailable 错误,

这是一个 EAGAIN 错误,与非阻塞 read() 一致。
手册页描述了当“文件描述符 ... 已被标记为非阻塞 (O_NONBLOCK),读取将阻塞”时会发生此错误。
read() 系统调用“会阻塞”,因为没有数据可以满足读取请求。

如果您坚持使用非阻塞模式,那么您的程序必须能够应对这种情况,这不是错误,而是暂时/暂时的状态。
但是对于多任务系统中的典型程序来说,阻塞模式是更简单和首选的操作模式。
您的程序应该如前所述进行修改。


串口终端的初始化有很多问题。


   tcgetattr(tty4, &oldtio);   // save current serial port settings

从不检查 tcgetattr()tcsetattr() 系统调用的返回值是否有错误。


   bzero(&newtio, sizeof(newtio)); // clear struct for new settings

从一个空的 termios 结构开始几乎总是一个坏主意。它可能似乎在某些系统上工作,但它不是可移植的代码。
初始化 termios 结构的正确方法是使用 tcgetattr() 中的值。
Setting Terminal Modes Properly
由于它已经被调用,你只需要newtio = oldtio 来复制结构。


    newtio.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
    newtio.c_iflag = IGNPAR | ICRNL;
    newtio.c_oflag = 0;
    newtio.c_lflag = ICANON;

更改这些标志的正确方法是启用或禁用各个属性,而不是分配常量。
对于规范模式,以下内容就足够了:

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

    newtio.c_lflag |= ICANON | ISIG;  /* canonical input */
    newtio.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
    
    newtio.c_iflag &= ~INPCK;
    newtio.c_iflag |= ICRNL;
    newtio.c_iflag &= ~(INLCR | IGNCR | IUCLC | IMAXBEL);
    newtio.c_iflag &= ~(IXON | IXOFF | IXANY);   /* no SW flowcontrol */

    newtio.c_oflag &= ~OPOST;

以下是设置波特率的首选方法:

    cfsetospeed(&newtio, B9600);
    cfsetispeed(&newtio, B9600);

如果未指定任何显着属性,则使用现有设置。
这可能导致不稳定的程序行为,例如有时有效,有时无效。


有趣的是,当我打开 minicom 时,如果我启动我的程序,minicom 将停止打印任何内容,并且即使我停止我的程序也会保持这种状态。我需要再次打开串行设置(Ctrl-A、P)并关闭它以使 minicom 恢复工作(似乎没有任何更改)。

串行终端不适合在多个进程之间共享。
有些termios属性必须在串口设备驱动中实现,没有共享端口的概念。最新的 termios 属性对设备有效。
当您在 minicom 启动后执行程序时,您正在破坏 minicom 期望执行的 termios 属性。
您正在使用其菜单将 termios 属性恢复到 minicom 的要求。

【讨论】:

  • 非常感谢!这是我第一次涉足这个领域,我想我有点不知所措(并且无可否认有些匆忙,这导致了一些非常糟糕的代码)。你的回答真的很有帮助:)
猜你喜欢
  • 2014-10-01
  • 2014-07-11
  • 1970-01-01
  • 1970-01-01
  • 2014-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多