c - 使用 unix STTY 更改后尝试更改 C 程序中的串行波特率时发生 I/O 错误

标签 c unix serial-port termios baud-rate

我注意到一些奇怪但可重现的东西。

我首先检查我的串行端口设置:

    bash-3.1# stty -F /dev/ttyS0
    speed 0 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>;
    min = 1; time = 0;
    -cread
    -brkint -icrnl -imaxbel
    -opost -onlcr
    -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

然后将速度更改为 1200bps:

bash-3.1# stty -F /dev/ttyS0 1200

然后我在函数中执行程序的这个片段来更改波特率:

fd=open(dev,O_NOCTTY | O_NONBLOCK | O_RDWR);
struct termios ser[1];
tcflush(fd,TCIFLUSH);
tcflush(fd,TCOFLUSH);
cfmakeraw(ser);
 // I call tcsetattr after each terminal setting to make sure its applied.
if (tcsetattr(fd,TCSANOW,ser) < 0){
    return -1;
}
cfsetspeed(ser,B9600);
if (tcsetattr(fd,TCSANOW,ser) < 0){
  return -2; //returns this after manually setting port via STTY
}

问题是波特率没有正确更改。事实上,我从函数返回 -2,并且 strerror(errno) 返回“输入/输出错误”。

程序执行后,我检查系统端口设置:

bash-3.1# stty -F /dev/ttyS0
speed 0 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>;
min = 1; time = 0;
-cread
-brkint -icrnl -imaxbel
-opost -onlcr
-isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

即使我特别要求 9600bps,它也会重置为零 bps。

为什么要这么做?如何以编程方式强制速度达到 9600bps?

最佳答案

您的代码中有很多错误。

  • 使用 O_NONBLOCK 打开 tty 设备,所以当您发出 ioctl 时调用( tc*attr(3) 调用会导致 ioctl(2) 系统调用,具体取决于您使用的 UNIX 风格),您不知道设备是否已经打开以能够进行 tc*attr(3)来电。这同样适用于 O_NOCTTY旗帜。您在不知道这些标志在开放系统调用中的功能的情况下放置了这些标志。 O_NOCTTY在 session 内部运行的程序中毫无用处,并且 O_NONBLOCK将使您的tc*attr(3)当尝试进行参数调整时,如果设备尚未打开,则调用返回错误 ( EAGAIN )。
  • 您没有检查 open(2) 的结果称呼。如果您尝试使用-1,这可能会出错。作为文件描述符( ENODEVENOTTYEBADFEINVALENXIO 等)
  • 您没有初始化 struct termios 的数据结构,所以这可能就是您收到错误的原因。正如您所展示的(您的示例代码片段不完整,这是告诉您阅读 How to create a Minimal, Complete, and Verifiable example 的一个原因) struct termios您使用的是在自动变量上声明的(因为它的声明嵌入到代码中),所以它肯定是未初始化的并且带有垃圾数据。您通常需要执行 tcgetattr()在其上初始化为正确的值,并能够在程序结束后恢复设置。
  • bash(1)使ioctl(2)当连接到 tty 设备时,在标准输入描述符上设置和获取 termios 参数。如果您使用标准输入,则必须考虑 bash(1) 的干扰。这使得您获得的值与您使用 stty 设置的值不同。 .
  • 在一般的 unix 操作系统中(恐怕在 Linux 中不是这样),最后一次关闭设备通常会将 tty 的参数重置为标准固定值,因此当您更改非标准输入设备时设置的标志(不是标准输入,当 stty 完成时它不是最后关闭的)使用 stty,这些参数重置为默认值一次 stty终止(在 tty 最后一次关闭时)。做一个sleep 999999999 </dev/ttyBlaBla &在发出 stty(1) 之前命令,因此在使用 sleep 设置后端口保持打开状态(通过 stty(1) 命令的重定向) .
  • 阅读 termios(3)页面,以便您可以从程序本身设置参数。仅当您的程序通常不处理设置参数时,您才不必以编程方式执行此操作。但这样改变终端参数就没有意义了,所以最好学习如何对设备参数进行编程。

正确的做法应该是这样的(从您的代码片段复制并编辑):

#include <string.h> /* for strerror */
#include <errno.h> /* for errno definition */

/* ... */

fd=open(dev,O_NOCTTY | O_NONBLOCK | O_RDWR);
if (fd < 0) {
    fprintf(stderr, "OPEN: %s (errno = %d)\n",
        strerror(errno), errno);
    return -1;
}
struct termios ser; /* why an array? */
tcflush(fd,TCIFLUSH); /* unneeded, you have not used the tty yet */
tcflush(fd,TCOFLUSH); /* idem. */
/******* THIS IS THE MOST IMPORTANT THING YOU FORGOT ***********/
int res = tcgetattr(fd, &ser); /* *****this initializes the struct termios ser***** */
if (res < 0) {
    fprintf(stderr, "TCGETATTR: %s (errno = %d)\n",
        strerror(errno), errno);
    return -2; /* cannot tcgetattr */
}
/***************************************************************/
cfmakeraw(&ser); /* now it is valid to set it */
cfsetspeed(&ser,B9600);  /* better do all in only one system call */
// I call tcsetattr after each terminal setting to make sure its applied.
/* nope, tcsetattr and tcgetattr are the only calls that make something 
 * on the tty, the rest only manipulate bits on the struct termios
 * structure, but don't do anything to the terminal, you begun with a
 * trashed struct termios, so it's normal you end with an error. */
if ((res = tcsetattr(fd, TCSANOW, &ser)) < 0){
    fprintf(stderr, "ERROR: %s (errno = %d)\n",
        strerror(errno), errno); /* better to know what happened. */
    return -3; /* couldn't tcsetattr */
}

最后,这段代码(作为您的第一个代码)尚未经过测试,主要是因为您没有发布完整的、最小的且可验证的示例。因此,在将其包含到代码中之前,您可能需要对其进行一些修改。并且请RTFM(完整阅读termios(3)的最后一个含义,也是最重要的:How to create a Minimal, Complete, and Verifiable example):)。另外,如果您使用 bash(1),请不要检查 stdin 上的 tty 设置。 ,因为它通常会恢复 tty命令退出后、发出提示之前的设置。

关于c - 使用 unix STTY 更改后尝试更改 C 程序中的串行波特率时发生 I/O 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56203882/

相关文章:

unix - sftp put -r 不工作,终端打印 "Entering myDirectory"然后什么也没有发生

c# - 使用 LINQ 查找串行设备列表

c++ - boost::asio::async_read_until 不调用处理程序

c# - 使用 C# 将连续的结构化数据写入二进制文件

c++ - 需要澄清这是否使用了 typedef

python - 防止文件描述符在 POSIX 系统上关闭

linux - 使用 sed 或 VIM 将空格替换为新行

c++ - 逆向工程遗留串行端口通信

c - 使用中序遍历删除二叉树

c# - ref 关键字和包装方法