【问题标题】:reading serial port blocks for unknown reason未知原因读取串行端口块
【发布时间】:2011-12-20 09:37:47
【问题描述】:

我正在尝试使用 Linux 下的 termios 框架通过 UART(usbserial)连接非接触式智能卡读卡器。该代码在 PC 上运行良好,但是当我在 ARM9 目标上进行交叉编译并试用时,它能够打开设备甚至将命令写入设备,但读取命令会无限期地阻塞。这是代码 sn-p :

int mifare_rdr_init(struct mifare_1K * ptr, char *rdr_devnode)
{   
    bzero(ptr, sizeof(struct mifare_1K));           // zero the entire structure
    // open serial device
    int fd = open(rdr_devnode, O_RDWR|O_NOCTTY );
    if (fd == -1) {
    perror("Failed to open serial device ");
    return 1;
    }
    ptr->serialfd = fd;                 // save file descriptor

    ptr->serialdev.c_iflag = 0;                 // no i/p flags
    ptr->serialdev.c_oflag = 0;                 // o/p flags
    ptr->serialdev.c_cflag = ( CS8 | CREAD | B38400 );      // 8 bits, receive enable, baud for rdr
    ptr->serialdev.c_lflag = ( ICANON );                // CANONICAL mode, means read till newline char '\n'.
    // control chars 
        // commented below line as suggested by A.H below, since it's not needed in CANONICAL mode
    // ptr->serialdev.c_cc[VMIN] = 1;               // read unblocks only after at least one received char.

    // flush all i/o garbage data if present
    tcflush(ptr->serialfd,TCIOFLUSH);

    int ret = 0;
    // apply settings
    ret = tcsetattr(ptr->serialfd,TCSANOW,&ptr->serialdev);
    if (ret == -1) {
        perror("tcsetattr() failed ");
        return 2;
    }
    return 0;
    }

int get_mifare_rdr_version(struct mifare_1K *ptr, char *data)
{
    // flush all i/o garbage data if present
    tcflush(ptr->serialfd,TCIOFLUSH);

    int chars_written = write(ptr->serialfd,"$1V\n",4);
    if( chars_written < 0 ) {
        perror("Failed to write serial device ");
        return 1;
    }
        printf("cmd sent, read version...\n");   // this prints, so I know cmd sent...
    int chars_read = read(ptr->serialfd,ptr->data_buf,14);
    if( chars_read < 0 ) {
        perror("Failed to read serial device ");
        return 2;
    }
    // copy data to user buffer
        printf("reading done.\n");    // this doesn't print...
    return 0;
}

mifare_1K 结构包含串行设备的文件描述符、termios 结构和我正在使用的各种缓冲区。我提到的设备是usb-to-serial(模块:ftdi_sio)设备。在termios的Canonical模式下配置在38400@8-N-1。

规范模式,因为来自阅读器的响应以“\n”结尾,因此在规范模式下处理更好,因为它会读取设备直到收到“\n”(如果我错了,请纠正我)。

我首先调用 init() fn,然后调用 get_rdr_version()。字符串“cmd sent, read version...”被打印出来,所以我知道它可以写,但不打印字符串“reading done”。之后。

另一件事是,如果我卸下读卡器并将该端口连接到另一台 PC 上的 gtkterm(串行端口终端程序),我不会在该 gtkterm 上收到“$1V\n”??!! .然后经过一点 RnD,我发现如果我重新启动连接读卡器的系统,那么只有我在另一个 Gtkterm 上得到那个 cmd "$1V\n"。如果我在不重新启动的情况下重试,则在该 Gkterm 上看不到该 cmd...一个线索,但尚未弄清楚。

是否类似于将 cmd 写入设备文件,但未排入实际设备?有什么办法可以检查这个吗?

任何帮助都深表感谢,因为我已经有一段时间被困在这个问题上了......谢谢。

更新:

好的,我已经通过稍微修改代码来让它工作,如下所示。

// open serial device
    int fd = open(rdr_devnode, O_RDWR|O_NOCTTY|O_NDELAY );  // O_NDELAY ignores the status of DCD line, all read/write calls after this will be non-blocking
    fcntl(fd,F_SETFL,0);   // restore read/write blocking behavior
    if (fd == -1) {
    perror("Failed to open serial device ");
    return 1;
    }

这是在我的 init() 函数中打开端口的代码的修改部分。两个变化:

1) O_NDELAY 被添加到 open() 调用的标志中,它忽略数据载波检测 (DCD) 线,以查看另一端是否已连接并准备好进行通信。这本来是用于MODEM的,我不需要,事实上,因为我使用的是usbserial,所以我根本没有它。但是这个标志也会进一步调用 read() 和 write() 作为非阻塞。需要注意的是,我原以为可以通过将 CLOCAL 添加到 termios 结构的 cflag 来解决这个问题,我尝试过但没有成功。

2) fcntl(fd,F_SETFL,0) 恢复进一步 read() 和 write() 调用的阻塞行为。

这个组合非常适合我。 唯一我没有将其发布为答案的原因是我还不明白为什么它在没有这种修改的情况下在 PC 上工作,因为它是相同的硬件。事实上,我可以使用 minicom 从 ARM9 TARGET 上的智能卡读卡器读取数据,但不是我的程序。我去查一下FT232BL的文档,看看DCD默认是什么状态。

无论如何,我在 Serial programming Guide for POSIX operating systems 上找到了这条信息。解释任何人???当然,我找到答案后会发布..

干杯:)

【问题讨论】:

  • 可能不是问题,但您可能需要将CLOCAL 添加到c_cflag。使用tcgetattr 获取当前设置(例如控制字符)而不是用 0 覆盖这些设置可能会更好。您可能还想尝试使用tcdrain 以确保您的程序等待数据传输。
  • 嗨 Hasturkun...我没有检查,因为我认为没有人表现出兴趣。无论如何,我检查了你的建议( tcdrain(), CLOCAL )......结果相同。但它在 PC 上运行,而不是在目标上运行的事实让我很感兴趣。欢迎任何更多建议...将在更新时发布。
  • “但事实上它在 PC 上运行,而它不在目标上..” -- 很简单,你的代码不符合 POSIX,所以不要指望它便携。见Setting Terminal Modes Properly

标签: linux embedded serial-port embedded-linux termios


【解决方案1】:

刚刚在带有 Telegesis USB 模块的 Raspberry Pi 上遇到了相同的症状,我将其添加为另一个数据点。

在我的情况下,原因是缺少 RTS 标志。 Telegesis 期望 CRTSCTS 流控制,并且不会在没有看到 RTS 的情况下向 Raspberry 发送任何数据。这里令人困惑的方面是 a) 相同的代码在 PC 上运行良好,b) 在第一次插入 Telegesis 时它在 Raspberry 上运行良好,但在随后打开 /dev/ttyUSB0 时不会看到任何数据由覆盆子。

出于某种原因,似乎在 ARM 上,RTS 标志在设备关闭时被清除,但没有再次设置,而在 x86/x64 上,RTS 标志保持设置。此处的修复只是设置 RTS 标志(如果尚未设置) - 例如

#include <sys/ioctl.h>
//...
int rtscts = 0;
if (ioctl (fd, TIOCMGET, &rtscts) != 0)
{
  // handle error
}
else if (!(rtscts & TIOCM_RTS))
{
  rtscts |= TIOCM_RTS;
  if (ioctl (fd, TIOCMSET, &rtscts) != 0)
  {
    // handle error
  }
}

我确实注意到,在您的情况下,您没有使用流量控制,因此上述内容很可能不适用。然而,正是您的问题和提到 minicom 的工作使我们找到了解决问题的方法 - 所以谢谢您!

【讨论】:

【解决方案2】:

你可能会检查三件事:

规范模式/非规范模式混合1

您正在混合规范模式和非规范模式的东西:

ptr->serialdev.c_lflag = ( ICANON );
// ...
ptr->serialdev.c_cc[VMIN] = 1;

termios(3) 联机帮助页说明了 VMIN:

VMIN规范读取的最小字符数。

很明显,你的超时不会像你想的那样工作。

规范模式/非规范模式混合2

此外,手册页还进一步说明如下:

这些符号下标值都是不同的,除了VTIME, VMIN 可能分别与 VEOL、VEOF 具有相同的值。在非 规范模式特殊字符含义被超时替换 意义。有关 VMIN 和 VTIME 的说明,请参见 下面是非规范模式。

所以请检查这些常量的定义对于您的两个平台是否不同。错误的设置可能会破坏 EOL/EOF 逻辑。 EOL 和 EOF 都可能导致从 read 返回。

未初始化c_cc

您的代码未正确初始化 c_cc 数组。您既没有阅读现有设置,也没有为规范模式所需的值提供合适的默认值。到目前为止显示的代码甚至没有清除这些值。因此可能会使用不可预测的值。

【讨论】:

  • 嗨,A.H.,您对 VMIN 的看法是正确的。在 CANONICAL 模式下不需要它,所以我通过评论编辑了这个问题。但是关于初始化结构,你可以看到我正在将包含 termios 结构的结构归零,所以我将整个结构初始化为零,我不会让它挂起。
  • @aditya:抱歉,我没有注意到bzero。但这加强了一点,即未配置规范模式所需的控制字符。
  • 非常正确,它们应该已经被初始化了。需要指出的是,我确实在我的一次试验中初始化了 VEOL、VEOL2 和 VEOF,因为我认为 read() 会阻塞 bcos,因为它没有获得默认终端 '\n',但无济于事。
猜你喜欢
  • 1970-01-01
  • 2016-07-01
  • 1970-01-01
  • 2018-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-10
  • 2013-09-09
相关资源
最近更新 更多