从 c 中的串口读取会打断行

reading from serial port in c breaks up lines

我正在尝试用 C 编写一个小程序,它将使用 select 命令从串行端口读取数据,以便它阻塞并等待输入。它的工作,除了它不断打破线,我不知道为什么。该设备被编程为不会打断线路,并且可以与实际的终端程序一起正常工作。我以前从未在 C 中做过串行通信,而且我在 Mac 上,所以这对我来说都是新的。我真的不知道去哪里寻找问题所在。

我有一些代码可以查找并列出串口。为了简单起见,我将其省略,所以如果有一个变量没有意义,那可能就是原因。以下是打开端口、设置属性并尝试从中读取的代码,以及从 Apple 站点复制的注释(抱歉):

/* this is based on a combination of 
 * and https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html
 */

static int OpenSerialPort(const char *deviceFilePath, int speed)

{

int         fileDescriptor = -1;
struct termios  options;
memset(&options, 0, sizeof(options)); // init it

// Open the serial port read/write, with no controlling terminal,
// and don't wait for a connection.
// The O_NONBLOCK flag also causes subsequent I/O on the device to
// be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fileDescriptor == -1)
{
    printf("Error opening serial port %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
    goto error;
}

// Note that open() follows POSIX semantics: multiple open() calls to
// the same file will succeed unless the TIOCEXCL ioctl is issued.
// This will prevent additional opens except by root-owned processes.
// See options(4) ("man 4 options") and ioctl(2) ("man 2 ioctl") for details.

if (ioctl(fileDescriptor, TIOCEXCL) == kMyErrReturn)
{
    printf("Error setting TIOCEXCL on %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
    goto error;
}

// Set raw input (non-canonical) mode, with reads blocking until either
// a single character has been received or a one second timeout expires.
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
// for details.

cfmakeraw(&options);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 5;

// The baud rate, word length, and handshake options can be set as follows:
cfsetspeed(&options, speed);   // Set 19200 baud
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as [=10=]0 chars
options.c_iflag &= ~IGNBRK;         // disable break processing
options.c_lflag = 0;                // no signaling chars, no echo,
// no canonical processing
options.c_oflag = 0;                // no remapping, no delays


options.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

options.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
options.c_cflag &= ~(PARENB | PARODD);      // shut off parity
options.c_cflag |= false;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CRTSCTS;

// Cause the new options to take effect immediately.
if (tcsetattr(fileDescriptor, TCSANOW, &options) == kMyErrReturn)
{
    printf("Error setting options attributes %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
    goto error;
}

// turn on blocking
if (fcntl(fileDescriptor, F_SETFL, 0) == kMyErrReturn)
{
    printf("Error clearing O_NONBLOCK %s - %s(%d).\n", deviceFilePath, strerror(errno), errno);
    goto error;
}


// Success:
return fileDescriptor;
// Failure:
error:
if (fileDescriptor != kMyErrReturn)
{
    close(fileDescriptor);
}
return -1;
}

int main(void)
{
int         fileDescriptor;
kern_return_t   kernResult; // these are Apple-specific
io_iterator_t   serialPortIterator; // Apple
char        deviceFilePath[MAXPATHLEN];
fd_set fdset; // make a file descriptor set
FD_ZERO (&fdset); // init it
char buf[1000]; // some strings are big

kernResult = GetDevices(&serialPortIterator);
printf("Devices on this system:\n");
kernResult = ListDevicePaths(serialPortIterator, deviceFilePath, sizeof(deviceFilePath));

IOObjectRelease(serialPortIterator);    // Release the iterator.

// Open the modem port, initialize the modem, then close it.
if (!deviceFilePath[0])
{
    printf("No modem port found.\n");
    return EX_UNAVAILABLE;
}

fileDescriptor = OpenSerialPort("/dev/cu.usbmodem1d1111", B230400);
FD_SET (fileDescriptor, &fdset); // add to file descriptor set

// now we're going to use select to only read from the file handle when there's data available
while (1)
{
    if (select (FD_SETSIZE, &fdset, NULL, NULL, NULL) < 0) // this will block the program until something is on the line
    {
        printf("select error\n");
    }
    read(fileDescriptor, buf, 1000);
    printf("%s\n", buf);
    memset(buf, '[=10=]', 1000);
}



// let's try to read from the serial port
   /* for (int i = 0; i <= 10; i++)
{
    char buf [100];
    int n = read(fileDescriptor, buf, sizeof buf);
    printf("%s\n", buf);
    //usleep ((7 + 25) * 100);
}*/
close(fileDescriptor);
printf("Modem port closed.\n");

return EX_OK;
}

预期输出:

    This is sample output.
    Hello.

我在上面的程序中实际得到的是:

    Thi
    s is sam
    ple output.
    Hel
    lo.

或类似的东西。每次都不一样。有时效果很好。这似乎是随机的。

所以我的问题是: 我究竟做错了什么?除了一条毯子,我还需要处理什么代码 "all of it?" 我不明白什么?我承认我并不真正理解这些库究竟是如何工作的。我假设(我知道,我知道)他们负责流量控制和错误等。但话又说回来,我从中复制的示例并没有完全解释这一点,所以我不知道。我真的不知道发生了什么。

问题是你正在读取任意数量的字节,然后用换行符分隔输出它们:

read(fileDescriptor, buf, 1000);
printf("%s\n", buf);

您打开了描述符 O_NONBLOCK,我不确定您的 fcntl 调用是否足以清除它。结果是 read 拉出,但是在那一刻恰好缓冲了很多字符,然后打印它们后跟一个换行符。

您可能不希望 read 处于阻塞模式,因为在读取 1000 个字符之前它可能不会 return。这可能更接近你想要的:

amt = read(fileDescriptor, buf, 1000);
if (amt > 0)
    write(1,buff,amt);
else
    break;

当然错误处理应该多很多

it keeps breaking up lines and I have no idea why.

如果您想从串行终端读取行,则必须对其进行配置。
相反,您已将其配置为非规范和非阻塞模式。
该代码与您声明的意图完全不符。

引自 Linux termios man 页面:

In canonical mode:
Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).

代码中清楚地注释了它使用的是非规范模式(即错误的模式):

// Set raw input (non-canonical) mode, with reads blocking until either
// a single character has been received or a one second timeout expires.
// See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
// for details.

cfmakeraw(&options);
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 5;

您需要删除这些行以获得规范模式并读取行而不是原始字节。

如果您期望 read() 到 return 完整的行,那么程序将不得不等待输入。这意味着您需要阻止 I/O.

// The O_NONBLOCK flag also causes subsequent I/O on the device to
// be non-blocking.
// See open(2) ("man 2 open") for details.
fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK);

需要从 open() 系统调用中删除 O_NONBLOCK 选项。

不管至少三位评论者写了什么,Linux 串行终端可以配置为读取行。您使用的是真正的操作系统,而不是 运行 微处理器上的裸机。您所要做的就是激活线路规则来扫描串行终端接收到的字符。
有关编程规范模式的完整详细信息,请参见 Serial Programming Guide for POSIX Operating Systemstermios man 页面。

您的代码还有几个问题需要更正:

  • 而不是 memset(&options, 0, sizeof(options)) 代码应该调用 tcgetattr() 来正确初始化结构。对于规范输入来说,这可能是一个严重的问题,因为现有代码会将所有控制代码规范归零,而不是具有正确的定义。
  • 代码应该执行按位运算(以保留现有设置),而不是直接赋值。参见 Setting Terminal Modes Properly
  • 需要扩展 read(fileDescriptor, buf, 1000) 语句以处理可能的错误和处理接收到的数据。
  • 来自 read() 系统调用的 return 代码需要检查是否存在任何错误情况。
  • 当未检测到错误时,return 代码指示缓冲区中 return 的字节数。请注意,输入不会被空字节终止,因此在附加空字节之前不应将字符串操作应用于缓冲区。

读取的代码应该是这样的:

 rc = read(fileDescriptor, buf, sizeof(buf) - 1);
 if (rc < 0) {
     /* handle error condition */
 } else {
     buf[rc] = '[=12=]';
     printf("%s", buf);
 }

由于buf[]分配了1000字节,read()请求最多可以return一行999 个字符长。