【问题标题】:Serial reads from a sensor using USB-serial cable in linux using C使用 C 在 Linux 中使用 USB 串行电缆从传感器串行读取
【发布时间】:2018-12-24 14:07:49
【问题描述】:

我一直在尝试使用 USB 到串行转换器从连接到我的树莓派的串行温度传感器读取响应。

我可以看到对传感器设备的写入似乎有效。但是,当我尝试从串行芯片回读时,读取失败并显示 -1。

我确实尝试使用 realterm 程序使用相同的波特率 9600 8 位无奇偶校验设置,并且能够按预期读取和写入十六进制值,请指出正确的方向。

void serial_write(char parameter,char value) {
    int fd;
    uint8_t bytes_wr;
    char wr_buffer[3];
    fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NDELAY); 

    if (fd == -1)
        ERROR("Error! in Opening ttyUSB0 \n");
    else
        DEBUG("ttyUSB0 Opened Successfully \n");

    struct termios SerialPortSettings;
    tcgetattr(fd, &SerialPortSettings);

    cfsetispeed(&SerialPortSettings,B9600);
    cfsetospeed(&SerialPortSettings,B9600);

    SerialPortSettings.c_cflag &= ~PARENB;
    SerialPortSettings.c_cflag &= ~CSTOPB;
    SerialPortSettings.c_cflag &= ~CSIZE;
    SerialPortSettings.c_cflag |=  CS8; 
    SerialPortSettings.c_cflag &= ~CRTSCTS;
    SerialPortSettings.c_cflag |= CREAD | CLOCAL;
    SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);  
    SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    SerialPortSettings.c_oflag &= ~OPOST;

    if ((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) 
        ERROR("ERROR ! in Setting attributes \n");
    else
        DEBUG("BaudRate=9600\tStopBits=1\tParity=none \n");

    wr_buffer[0] = write;
    wr_buffer[1] = parameter;
    wr_buffer[2] = value;

    bytes_wr = write(fd, wr_buffer,sizeof(wr_buffer));
    DEBUG("Total Bytes written: %d \n", sizeof(wr_buffer));

    close(fd);
}

上述函数似乎按预期写入串行端口,但是当我尝试读取时,读取失败并显示 -1

char serial_read(char parameter) {
    int fd, read_length, i;
    uint8_t bytes_wr;
    char wr_buffer[2];
    fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NDELAY); 

    if (fd == -1)
        ERROR("Error! in Opening ttyUSB0 \n");
    else
        DEBUG("ttyUSB0 Opened Successfully \n");

    struct termios SerialPortSettings;
    tcgetattr(fd, &SerialPortSettings);

    cfsetispeed(&SerialPortSettings,B9600);
    cfsetospeed(&SerialPortSettings,B9600);

    SerialPortSettings.c_cflag &= ~PARENB;
    SerialPortSettings.c_cflag &= ~CSTOPB;
    SerialPortSettings.c_cflag &= ~CSIZE;
    SerialPortSettings.c_cflag |=  CS8; 
    SerialPortSettings.c_cflag &= ~CRTSCTS;
    SerialPortSettings.c_cflag |= CREAD | CLOCAL;
    SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);  
    SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    SerialPortSettings.c_oflag &= ~OPOST;

    if ((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) 
        ERROR("ERROR ! in Setting attributes \n");
    else
        DEBUG("BaudRate=9600\tStopBits=1\tParity= none\n");

    wr_buffer[0] = read;
    wr_buffer[1] = parameter;

    bytes_wr = write(fd, wr_buffer,sizeof(wr_buffer));
    DEBUG("Total Bytes written: %d \n", sizeof(wr_buffer));
    usleep(8000);
    tcflush(fd,TCIFLUSH);
    char rd_buffer[4];
    read_length = read(fd, rd_buffer,sizeof(rd_buffer));
    DEBUG("Total bytes read = %d \n",read_length);

    for (i==0;i<read_length;i++){
        DEBUG("rd_buffer[%d]=%x \n",i,rd_buffer[i]);
    }
    close(fd);
    return rd_buffer[0];
}

使用 realterm windows 应用程序,所有写入和读取似乎都可以正常工作。

【问题讨论】:

  • 为什么要睡觉?为什么选择 tcflush? '返回 rd_buffer;' - 不,返回本地数组是 UB
  • 嗨对不起我的实际程序返回 rd_buffer[0] 。我试图删除 tcflush 以删除缓冲区中存储的先前数据。我最初没有睡觉,但我看到了一些例子,他们说我们必须等待数据写入才能读取。
  • 您的代码有错误(因为您从不良示例中复制)。您的 termios 初始化不正确且不完整;请参阅stackoverflow.com/questions/51195829/… 您使用 tcflush() 而不是 tcdrain() 是有问题的。您选择的非阻塞 I/O 处理不当。如果您不了解要求,请使用阻塞 I/O。

标签: c linux raspberry-pi serial-port termios


【解决方案1】:

来自 open(2) 手册页:

   O_NONBLOCK or O_NDELAY
          When possible, the file is opened in nonblocking mode.
          Neither the open() nor any subsequent operations on the file
          descriptor which is returned will cause the calling process to
          wait.

对于串行连接,最终结果将是,如果您要求从串行端口读取一些字节并且没有等待字符,那么 read 将返回 -1 并且 'errno' 可能是 EAGAIN 或EWOULDBLOCK。

所以你的 usleep(8000) 可能是试图等待足够长的时间让设备响应,但设备可能没有数据给你;特别是如果它处于 adc 操作的中间,它可能需要超过 8 毫秒。

你可以做一些事情:

你可以(在伪代码中):

int retries=10;
while(retries--) {
    read_length = read(fd, rd_buffer,sizeof(rd_buffer));
    if(read_length > 0)
        break;
    usleep(1000);
}

不幸的是,这样做的一个副作用是,如果温度传感器正在向您发送一个冗长的字符串,并且您的程序 read()s 而温度传感器仍在写入,您将得到一个部分字符串。因此,如果您知道等待接收的字符串的长度,您可以使用 ioctl() 找出等待的字符数:

ioctl(fd, FIONREAD, &bytes_avail);

所以伪代码看起来更像:

int retries=10;
int bytes_avail=0;
while(retries--) {
    if (ioctl(fd, FIONREAD, &bytes_avail) < 0) {
        fprintf(stderr, "ioctl failed\n");
        return;   // Do something here
    }
    if (bytes_avail >= sizeof(rd_buffer)) {
        read_length = read(fd, rd_buffer,sizeof(rd_buffer));
        if(read_length > 0)
            break;
    }
    usleep(1000);
}

如果温度传感器发送以换行符或回车符结尾的 ascii 字符串,则代码看起来会有所不同。

【讨论】:

  • 是否有一种确定的方法可以使读取工作始终如一。我看到使用方法 1 有一个警告,我最终可能会得到部分字符串,并且我不知道我正在等待的字符串的长度(它因参数而异)所以我可能无法使用方法 2要么。
  • Linux 是一个中断/事件驱动的操作系统,但你提倡使用轮询?使用非阻塞 I/O 然后轮询/休眠来检查系统缓冲区中是否有可用的数据是对该技术的误用并且效率低下。阻塞 I/O 会更加实用和高效。这只是 OP 代码问题的一部分。
  • OP 试图做的事情有很多问题。我正在回答读取返回-1的问题。也许 OP 正在从一个线程调用 serial_read() 或者你有什么。这让我觉得超出了问题的范围。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多