I/O 在使用 unix STTY 更改后尝试在 C 程序中更改串行波特时出错

I/O error when trying to change serial baud in C program after changing with unix STTY

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

我先检查一下我的串口设置:

    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
}

问题是波特率没有正确改变。事实上,我从函数和 strerror(errno) returns "input/output error" 中返回了 -2。

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

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 标志。您在不知道 open 系统调用的功能的情况下放置了这些标志。 O_NOCTTY 在来自会话内部的 运行 程序中是无用的,并且 O_NONBLOCK 将使您对 return 的 tc*attr(3) 调用出现错误(EAGAIN) 如果设备还没有打开,在尝试进行参数调整时。
  • 您没有检查 open(2) 调用的结果。如果您尝试使用 -1 作为文件描述符(ENODEVENOTTYEBADFEINVALENXIO,这可能会出错等等)
  • 你没有初始化 struct termios 结构的数据,所以这可能是你得到错误的原因。如您所示(您的示例代码片段不完整,告诉您阅读 How to create a Minimal, Complete, and Verifiable example 的一个原因)您使用的 struct termios 是在自动变量上声明的(因为它的声明嵌入到代码中)所以它肯定是未初始化的并且带有垃圾数据。您通常需要对其执行 tcgetattr() 以初始化为正确的值,并能够在程序结束后恢复设置。
  • 当连接到 tty 设备时,
  • bash(1) 使 ioctl(2)s 在标准输入描述符上设置和获取 termios 参数。如果你正在使用stdin,你必须考虑bash(1)的干扰。这使得您获得的值与您使用 stty.
  • 设置的值不同
  • 在一般的 unix 操作系统中(恐怕在 linux 中不是这样)最后一次关闭设备通常会将 tty 的参数重置为标准的固定值,所以当你设置的标志使用 stty 更改非标准输入设备(不是标准输入,stty 完成时未最后关闭),一旦 stty 终止(在 tty 最后关闭时),这些参数将重置为默认值。在发出 stty(1) 命令之前执行 sleep 999999999 </dev/ttyBlaBla &,以便在使用 stty(1).
  • 设置后端口保持打开状态(通过 sleep 命令的重定向)
  • 阅读 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 设置,然后再发出提示。