Последовательное чтение с датчика с помощью последовательного USB-кабеля в Linux с использованием C

Я пытался прочитать ответы последовательного датчика температуры, подключенного к моему raspberry pi, с помощью преобразователя USB в последовательный.

Я вижу, что запись на сенсорное устройство работает. Однако, когда я пытаюсь прочитать обратно с последовательного чипа, чтение не выполняется с -1.

Я попытался использовать ту же скорость передачи 9600 8 бит без настроек четности с помощью программы realterm и смог читать и записывать шестнадцатеричные значения, как и ожидалось, любезно укажите мне в правильном направлении.

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 все операции записи и чтения работают нормально.


person A.R    schedule 24.12.2018    source источник
comment
Зачем уснуть? Почему tcflush? 'return rd_buffer;' - нет, возвращение локального массива - UB   -  person Martin James    schedule 24.12.2018
comment
Привет, извините Моя настоящая программа возвращает rd_buffer [0]. Я попытался удалить tcflush, чтобы удалить предыдущие данные, хранящиеся в буфере. Сначала я не спал, но я видел несколько примеров, в которых говорилось, что мы должны дождаться записи данных перед чтением.   -  person A.R    schedule 24.12.2018
comment
В вашем коде есть ошибки (потому что вы скопировали из плохих примеров). Инициализация вашего терминала неправильная и неполная; см. stackoverflow.com/questions/51195829/ Использование вами tcflush () вместо tcdrain () проблематично. Ваш выбор неблокирующего ввода-вывода не обрабатывается должным образом. Если вы не понимаете требований, используйте блокировку ввода-вывода.   -  person sawdust    schedule 25.12.2018


Ответы (1)


Из man-страницы 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.

Для последовательного соединения конечным результатом будет то, что если вы попросите прочитать какое-то количество байтов из последовательного порта и нет ожидающих символов, тогда чтение вернется с -1, а 'errno', вероятно, будет EAGAIN или EWOULDBLOCK.

Таким образом, ваш usleep (8000), вероятно, был попыткой подождать достаточно долго, пока устройство не ответит, но на устройстве может не быть данных для вас; особенно если он находится в середине операции АЦП, это может занять больше 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, которая заканчивается символом новой строки или символом возврата каретки, тогда код будет выглядеть иначе.

person MumbleBuns    schedule 24.12.2018
comment
есть ли детерминированный способ добиться стабильной работы чтения. Я вижу, что есть предостережение при использовании метода 1, когда я могу получить частичные строки, и я не знаю длины строк, которые я жду (она варьируется от параметра к параметру), поэтому я не смогу использовать метод 2 либо. - person A.R; 24.12.2018
comment
Linux - это операционная система, управляемая прерываниями / событиями, но вы выступаете за использование опроса? Использование неблокирующего ввода-вывода и последующего опроса / ожидания для проверки наличия данных в системном буфере является неправильным и неэффективным. Блокирование ввода-вывода было бы более практичным и эффективным. И это только часть проблем с кодом OP. - person sawdust; 25.12.2018
comment
Было много проблем с тем, что пытался сделать OP. Я отвечал на вопрос о чтениях, возвращающих -1. Возможно, OP вызывает serial_read () из потока или что у вас есть. Мне кажется, что это выходит за рамки вопроса. - person MumbleBuns; 25.12.2018