从c中的串口读取会破坏线路

时间:2015-02-23 20:39:00

标签: c macos serial-port

我试图在C中编写一个小程序,它将使用select命令从串口读取,以便阻塞并等待输入。它正在工作,除了它不断分裂线,我不明白为什么。该设备的编程不会破坏线路,并且可以与实际的终端程序一起使用。我之前从未在C中进行过串行通信,而且我在Mac上也是如此,所以对我来说这都是新手。我真的不知道在哪里找到出错的地方。

我有一些代码可以找到并列出串口。为了简单起见,我会把它留下来,所以如果有一个没有意义的变量,那可能就是原因。以下是打开端口,设置属性并尝试从中读取的代码,以及Apple网站上复制的评论(对不起):

/* 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).\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 \000 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, '\0', 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.

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

所以我的问题是: 我究竟做错了什么?除了毯子之外我还需要做什么代码?#34;所有这些代码?&#34;我不明白的是什么?我承认我并不真正了解这些图书馆是如何运作的。我假设(我知道,我知道)他们负责流量控制和错误等等。但话说回来,我所复制的例子并没有完全解释,所以我不知道。我只是不知道发生了什么。

2 个答案:

答案 0 :(得分:1)

  

它不断分裂线,我不明白为什么。

如果您想从串口读取线路,则必须对其进行配置才能这样做 相反,您已将其配置为非规范和非阻止模式 该代码根本不符合您声明的意图。

引用Linux termios man 页面:

  

在规范模式中:
  输入逐行提供。一个输入线可用   对行分隔符的类型(NL,EOL,EOL2;或行开头的EOF)。除EOF外,行分隔符包含在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()返回完整的行,那么程序将不得不等待输入。这意味着您需要阻止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 Systems termios man 页面中找到。

您的代码还有一些问题需要纠正:

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

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

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

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

答案 1 :(得分:0)

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

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

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

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

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

当然,应该有更多的错误处理。