从 c 中的串行端口读取会破坏行 [英] reading from serial port in c breaks up lines

查看:14
本文介绍了从 c 中的串行端口读取会破坏行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to write a little program in C that will read from the serial port using the select command so that it blocks and waits for input. It's working, except it keeps breaking up lines and I have no idea why. The device is programmed not to break up the lines and works fine with actual terminal programs. I've never done serial communication in C before, and I'm on a Mac, so it's all new to me. I really have no idea where to even look for what's going wrong.

我有一些代码可以查找和列出串行端口.为简单起见,我将把它排除在外,所以如果有一个没有意义的变量,那可能就是原因.这是打开端口、设置属性并尝试从中读取的代码,并附有从 Apple 网站复制的注释(对不起):

I have some code that finds and lists serial ports. I'll leave that out for simplicity, so if there's a variable that doesn't make sense, that might be why. Here is the code that opens the port, sets attributes, and tries to read from it, complete with copied comments from Apple's site (sorry):

/* this is based on a combination of http://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c
 * 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).
", 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).
", 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 00 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).
", deviceFilePath, strerror(errno), errno);
    goto error;
}

// turn on blocking
if (fcntl(fileDescriptor, F_SETFL, 0) == kMyErrReturn)
{
    printf("Error clearing O_NONBLOCK %s - %s(%d).
", 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:
");
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.
");
    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
");
    }
    read(fileDescriptor, buf, 1000);
    printf("%s
", buf);
    memset(buf, '', 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
", buf);
    //usleep ((7 + 25) * 100);
}*/
close(fileDescriptor);
printf("Modem port closed.
");

return EX_OK;
}

预期输出:

    This is sample output.
    Hello.

我在上面的程序中实际得到了什么:

What I actually get in the above program:

    Thi
    s is sam
    ple output.
    Hel
    lo.

或者类似的东西.每次都不一样.有时它工作正常.似乎是随机的.

Or something like that. It's different each time. Sometimes it works fine. It seems to be random.

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

So my questions are: What am I doing wrong? What code do I need to work on aside from just a blanket "all of it?" What am I not understanding? I admit I don't really understand how these libraries work, exactly. I am assuming (I know, I know) that they take care of flow control and errors and so on. But then again, the examples I've copied from didn't exactly explain that so I don't know. I just don't really know what's going on.

推荐答案

它总是断线,我不知道为什么.

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

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

If you wants to read lines from the serial terminal, then you have to configure it to do so.
Instead you have configured it to be in non-canonical and non-blocking mode.
The code does not match your stated intentions at all.

引用 Linux termios ma​​n 页面:

Quoting from the Linux termios man page:

在规范模式下:
输入是逐行提供的.输入线是可用的,当一个行分隔符的类型(NL、EOL、EOL2;或在行首的 EOF).除了 EOF 的情况外,行分隔符包含在 read(2) 返回的缓冲区中.

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).

代码中明确注释为使用非规范模式(即错误模式):

The code is clearly commented that it is using non-canonical mode (i.e. the wrong mode):

// 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;

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

You need to remove these lines to get canonical mode and read lines instead of raw bytes.

如果您希望 read() 返回完整的行,那么程序将不得不等待输入.这意味着您需要阻塞 I/O.

If you expect the read() to return complete lines, then the program will have to wait for input. That means that you need blocking 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);

O_NONBLOCK 选项需要从 open() 系统调用中移除.

The O_NONBLOCK option needs to be removed from the open() syscall.

尽管至少有三位评论者写过,Linux 串行终端可以配置为读取行.您使用的是真正的操作系统,而不是在微处理器上运行裸机.您只需启动线路规则,扫描串口终端接收到的字符即可.
规范模式编程的完整细节可以在 POSIX 操作系统串行编程指南termios ma​​n 页面.

In spite of what at least three commenters have written, a Linux serial terminal can be configured to read lines. You are using a real operating system, and not running bare-metal on a microprocessor. All you have to do is activate the line discipline to scan the characters received by the serial terminal.
Full details for programming canonical mode can be found in Serial Programming Guide for POSIX Operating Systems and the termios man page.

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

There are also several issues with your code that should be corrected:

  • 代码应该调用 tcgetattr() 而不是 memset(&options, 0, sizeof(options)) 以正确初始化结构.这对于规范输入可能是一个严重的问题,因为现有代码会将所有控制代码规范归零,而不是具有正确的定义.
  • 代码应该执行按位运算(为了保留现有设置),而不是直接赋值.请参阅正确设置终端模式.
  • read(fileDescriptor, buf, 1000) 语句需要扩展处理可能的错误和处理接收到的数据.
  • 需要检查 read() 系统调用的返回码是否存在任何错误情况.
  • 当没有检测到错误时,返回码表示缓冲区中返回的字节数.请注意,输入不会以空字节终止,因此在添加空字节之前不应将字符串操作应用于缓冲区.
  • Instead of memset(&options, 0, sizeof(options)) the code should be calling tcgetattr() to properly initialize the structure. This can be a serious issue for canonical input, as the existing code will have zeroed out all of the control code specifications instead of having proper definitions.
  • Instead of direct assignments, the code should be performing bit-wise operations (in order to preserve existing settings). See Setting Terminal Modes Properly.
  • The read(fileDescriptor, buf, 1000) statement needs to be expanded to handle possible errors and to deal with the received data.
  • the return code from the read() syscall needs to be checked for any error conditions.
  • when no error is detected, then the return code indicates the number of bytes returned in the buffer. Note that the input will not be terminated by a null byte, so string operations should not be applied to the buffer until a null is appended.

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

The read code should something like:

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

由于为 buf[] 分配了 1000 个字节,因此 read() 请求可以返回长达 999 个字符的行.

Since buf[] is allocated for 1000 bytes, the read() request can return a line up to 999 characters long.

这篇关于从 c 中的串行端口读取会破坏行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆