【问题标题】:Only able to read one byte via serial只能通过串口读取一个字节
【发布时间】:2012-10-05 13:40:14
【问题描述】:

我的机器上有一个 C/Python 设置,我正在对串行通信进行一些测试,但由于某种原因,我从不读取超过 1 个字节。

我的设置:我有一台 Windows 7 机器,在虚拟机中运行 OpenSUSE。我有 2 个 USB-RS232 转换器和它们之间的适配器(所以它是从一个 USB 端口到另一个的环路)。

在 Windows 端,我能够让它们通过 Python 到 Python 和 C 到 Python 相互通信。一旦我使用 Linux VM,我就可以从 C (Linux) 到 Python (Windows) 获取数据,但是当我反过来做时,我只能得到 1 个字节。我认为我打开文件或在 Linux C 代码上执行读取的方式有问题,但我不确定可能是什么问题。

Python 代码(使用 PySerial):

>>> import serial
>>> ser = serial.Serial(3)
>>> ser
Serial<id=0x2491780, open=True>(port='COM4', baudrate=9600, bytesize=8, 
parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
>>> ser.read(5)
'Hello'
>>> ser.write("hi you")
6L

C 代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>

int open_port()
{
    int fd;
    fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
    if(fd < 0)
      perror("open_port: Unable to open /dev/ttyUSB0 - ");
    else
      fcntl(fd, F_SETFL, 0);
    return fd;
}

int swrite(int fd, char * str)
{
    int n;
    n = write(fd, str, strlen(str));
    if (n<0)
        printf("write() of %d bytes failed\n", strlen(str));
    return n;
}

int main()
{
    int fd, databytes;
    char buf[100] = {0};
    struct termios options;

    fd = open_port();

    //Set the baud rate to 9600 to match
    tcgetattr(fd, &options);
    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);
    tcsetattr(fd, TCSANOW, &options);
    tcgetattr(fd, &options);

    databytes = swrite(fd, "Hello");
    if(databytes > 0)
      printf("Wrote %d bytes\n", databytes);

    databytes = read(fd, buf, 100);
    if(databytes < 0)
      printf("Error! No bytes read\n");
    else
      printf("We read %d bytes, message: %s\n", databytes, buf);

    close(fd);

    return 0;
}

我要回来了:

mike@linux-4puc:~> gcc serial_com.c
mike@linux-4puc:~> ./a.out 
Wrote 5 bytes
We read 1 bytes, message: h

所以 Linux->Windows 写入工作正常,python 显示正确的“Hello”字符串,但由于某种原因,我在 Windows->Linux 方面只得到一个字节。

有人发现有什么不对吗?

编辑:
根据我得到的反馈,我尝试对代码进行两次调整。听起来我不能保证所有的数据都会在那里,所以我试过了:

1) 睡觉

    if(databytes > 0)
      printf("Wrote %d bytes\n", databytes);
    sleep(15);                 // Hack one to get the data there in time, worked
    databytes = read(fd, buf, 100);

2) 一个while循环

while(1){  // Hack two to catch the data that wasn't read the first time. Failed
           // this only saw 'h' like before then sat waiting on the read()
  databytes = read(fd, buf, 100);
  if(databytes < 0)
    printf("Error! No bytes read\n");
  else
    printf("We read %d bytes, message: %s\n", databytes, buf);
}

似乎循环不起作用,所以未读取的数据会被丢弃吗? /编辑

【问题讨论】:

  • 重新阅读你原来的问题——听起来你从来没有能够从 linux 端接收数据。您是否确认 linux 驱动程序完全正常工作? USB-RS232 转换器可能是非常挑剔的野兽,有些芯片组根本没有得到很好的支持。在进一步排除您自己的代码故障之前,我会使用 minicom 之类的工具测试端口,以确保设备正常工作。

标签: python c windows linux serial-port


【解决方案1】:

来自read(2) manual

成功时,返回读取的字节数(零表示结束 文件),并且文件位置由这个数字提前。 这不是 如果此数字小于请求的字节数,则会出错; 例如,这可能会发生,因为实际上可用的字节更少 现在(可能是因为我们接近文件结尾,或者因为我们 正在从管道或终端读取)

换句话说,由于您在写入后立即从套接字读取,并且您的计算机比串行端口快得多,很可能只有一个字符可供读取和@987654322 @ 只返回那个字符。

【讨论】:

  • 感谢您的意见。请查看我的编辑,但从您的帖子中听起来数据根本不存在。添加sleep() 以留出额外时间证实了这一点……但为什么我不能循环读取数据?似乎失败了。 (上面的测试#2)
  • @Mike 没有方便测试的串行端口,但为了确保这不是问题,您可以在使用%s 打印之前零终止buf 吗?
  • 一开始我已经将它初始化为 0:char buf[100] = {0}; 所以在 read() 之后,buf 中的下一个字符应该已经是 '\0',或者我们是在要求别的东西吗?
  • @Mike 哎呀,错过了初始化。再次调用read() 时,您确实应该收到接下来的几个字节的数据。除了“h”行之外,没有其他输出似乎表明串行握手已停止其余部分的传输。 Python 是否仍在为写入行返回“6L”?
【解决方案2】:

read 的手册页说

...尝试读取最多 count 个字节...

您的代码看起来假设完整的缓冲区将始终由单个 read 返回;它对通过多次调用返回的数据有效。

检查 read 返回 -1 和 errno == EINTR 并在此之后重试也是一个好习惯(如果在 GNU 系统上运行,请使用 TEMP_FAILURE_RETRY)。 read 如果被信号中断,可能会返回瞬态错误。

【讨论】:

  • valid for the data to be returned over several calls - 这不意味着我上面的编辑#2应该有效吗?如果我循环并且第一次只获得 1 个字符,那么我应该在第二次读取时获得更多的字符?
  • @Mike 我希望您在第二次阅读时获得更多字符。我回答的最后一段解释了为什么有时情况并非如此。在 errno == EINTR 的情况下,您可以尝试再次阅读吗?
【解决方案3】:

正如其他人所回答的那样,C 的 read() 函数通过仅返回一个字节来履行其合同。

C 的 read() 和 Python 的 read() 函数完全不同。

PySerial 说,关于 read(),

从串口读取 size 个字节。如果设置了超时,它可能会根据请求返回更少的字符。没有超时,它将阻塞,直到读取请求的字节数。

虽然 C API 不做这样的保证。它将返回缓冲区中可用的任何字符(即使是 0,如果还没有的话)。

如果您希望 C 端的行为类似于 Python 端,则需要使用不同的函数。像这样的:

int read_exact_chars(int fildes, char *buf, size_t nbyte)
{
    ssize_t chars_read;
    int chars_left = nbyte;
    while (chars_left) {
        chars_read = read(fildes, buf, chars_left)
        if (chars_read == -1) {
            /* An error occurred; bail out early. The caller will see that
               we read fewer bytes than requested, and can check errno
             */
            break;
        } else {
            buf += chars_read;
            chars_left -= chars_read;
        }
    }
    /* return the actual number of characters read */
    return nbyte - chars_left;
}

【讨论】:

  • 出于所有意图和目的,我的编辑(第二个示例)与您的不一样吗?我继续阅读和显示,但只有第一个字节进来。
  • 好吧,也许不是出于所有的意图和目的:) 我的代码正在模拟python read() 函数的行为;只要端口打开,您的端口就会读取所有可用的内容。但是,是的,他们都在循环阅读,不应该在第一个字符之后停止。这可能表明还有其他问题。
【解决方案4】:

得到了一些很好的答案并对此进行了煽动(周围都是+1!),虽然所有答案都得出了正确的结论(关于阅读没有得到数据),但没有一个输入实际上“解决”了我遇到的问题.

这就是我最终实现此功能的方法:

termios 结构中的设置让我很生气。当我设置 一些 标志时,我并没有设置所有这些标志。所以这个:

tcgetattr(fd, &options);
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
tcsetattr(fd, TCSANOW, &options); 

改成这样:

tcgetattr(fd, &options);
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
options.c_iflag = 0; // Disable Input flags
options.c_lflag = 0; // Disable Local mode flags
options.c_oflag = 0; // Disable Output flags
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8; //Data bits per character (8)
options.c_cc[VMIN] = 50;  // Wait for 50 characters or
options.c_cc[VTIME] = 50; // Wait for 5 seconds
options.c_cflag |= (CLOCAL | CREAD | HUPCL);   // Ignore modem status lines, 
                                               // enable receiver, 
                                               // and hang up on last close
options.c_cflag &= ~(PARENB | PARODD); //Clearing even and odd parity
options.c_cflag &= ~CSTOPB;            //Clear double stop bits
tcsetattr(fd, TCSANOW, &options);

现在通过这些更改,我可以在我用 Python 编写的 C Linux 代码中获取数据。

我使用这两个来源了解了很多关于termios 结构的选项的“事实”:

  1. flag description
  2. Quick Overview

【讨论】:

    猜你喜欢
    • 2014-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    相关资源
    最近更新 更多