我正在尝试在Linux下使用termios框架通过UART(usbserial)接口与一种非接触式智能卡读卡器进行通信。代码在PC上运行良好,但是当我将其交叉编译并在ARM9目标上尝试执行时,它可以打开设备并向设备写入命令,但读取命令会无限期地阻塞。以下是代码片段:
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转串口模块(ftdi_sio)。它以termios的规范模式配置在38400@8-N-1。因为读卡器的响应以'\n'结尾,所以最好采用规范模式处理。因为采用该模式可以等待接收到'\n'之后再读取设备(如果我错了,请纠正我)。
首先我调用init()函数,然后再调用get_rdr_version()函数。当打印出"cmd sent, read version..."时,我知道它能够写入,但是在此之后并未打印出字符串"reading done."。
另外一件事情是,如果我移除读卡器并将该端口连接到另一台PC上的gtkterm(串口终端程序),我就无法在gtkterm上接收到"$1V\n"。经过一些研究后,我发现只有在重启连接读卡器的系统后,我才会在另一台Gtkterm上收到该命令"$1V\n"。如果我不重新启动系统,那么该命令就不会出现在Gkterm上。这是个线索,但我还没有搞清楚原因。
难道是命令被写入设备文件中,但没有被传输到实际设备中吗?有没有办法检查这个问题?
非常感谢任何帮助,因为我已经卡在这里一段时间了......谢谢。
更新:
好的,我通过修改代码稍微调整了一下,现在它正常工作了。
// 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)在open()调用的标志中添加了O_NDELAY,它忽略数据载波检测(DCD)线以查看另一端是否连接并准备好通信。这最初用于MODEM,但实际上我不需要,事实上,我根本没有使用usbserial。但是,此标志还使进一步的read()和write()调用为非阻塞。需要注意的是,我曾认为将CLOCAL添加到termios结构体的cflag中会处理此问题,但尝试后没有成功。
2)fcntl(fd,F_SETFL,0)恢复了进一步的read()和write()调用的阻塞行为。
这种组合对我来说完美地工作。唯一的原因是我还不理解为什么它在PC上没有进行此修改也能够工作,因为硬件是相同的。实际上,我能够使用minicom从ARM9目标读取智能卡读卡器中的数据,但无法使用我的程序。我将查看FT232BL文档,以查看DCD的默认状态。
无论如何,我在POSIX操作系统串行编程指南上找到了这些信息。有人能解释一下吗?当然,我找到答案后会发布答案。
干杯 :)
CLOCAL
添加到c_cflag
。最好使用tcgetattr
获取当前设置(例如控制字符),而不是用 0 覆盖这些设置。您还可以尝试使用tcdrain
确保程序等待数据传输完成。 - Hasturkun