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

时间:2019-05-19 00:33:26

标签: 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?

1 个答案:

答案 0 :(得分:1)

您的代码中有很多错误。

  • 您使用O_NONBLOCK打开tty设备,因此发出ioctl调用时(tc*attr(3)调用会导致ioctl(2) syscall,具体取决于您使用的unix风格),您不知道该设备是否已经打开即可进行tc*attr(3)通话。 O_NOCTTY标志也是如此。您将这些标志放在打开系统调用中时不知道它们的功能是什么。 O_NOCTTY在从会话内部运行的程序中是无用的,如果设备没有运行,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参数。如果您正在使用stdin,则必须考虑bash(1)的干扰。这使您获得的值与通过stty设置的值有所不同。
  • 在一般的unix操作系统中(恐怕在linux中不是这样),最后一个靠近设备的设备通常会将tty的参数重置为标准固定值,因此在更改非stdin设备时设置的标志(而不是标准输入(在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的设置。