Хобрук: Ваш путь к мастерству в программировании

Лучший способ читать потоки данных с разными размерами пакетов из последовательного порта в С++

Я работаю над прошивкой сенсорной платы ATMEL (акселерометр и гироскоп) и пытаюсь прочитать данные на платформе в Ubuntu.

На данный момент прошивка такая:

Ubuntu отправляет символ «D», а прошивка в ответ отправляет обратно 20 байтов данных, которые заканчиваются на «\n», затем ubuntu использует serialport_read_until(fd, buff, '\n') и предполагает, что buff[0] является нулевым байтом и так далее. Частота сбора 200 Гц. НО, используя этот метод, иногда я получаю поврежденные значения, и он не работает должным образом. Также в Ubuntu много ошибок «Невозможно записать на последовательный порт».

Я нашел пример кода от ATMEL для прошивки и там данные отправляются разными пакетами и непрерывно (не дожидаясь, пока компьютер их запросит) структура такая:

    void adv_data_send_3(uint8_t stream_num, uint32_t timestamp,
        int32_t value0, int32_t value1, int32_t value2)
{
    /* Define packet format with 3 data fields */
    struct {
        adv_data_start_t start;       /* Starting fields of packet */
        adv_data_field_t field [3];   /* 3 data fields */
        adv_data_end_t end;           /* Ending fields of packet */
    } packet;

    /* Construct packet */
    packet.start.header1 = ADV_PKT_HEADER_1;
    packet.start.header2 = ADV_PKT_HEADER_2;
    packet.start.length  = cpu_to_le16(sizeof(packet));
    packet.start.type    = ADV_PKT_DATA;
    packet.start.stream_num = stream_num;
    packet.start.time_stamp = cpu_to_le32(timestamp);

    packet.field[0].value = cpu_to_le32(value0);
    packet.field[1].value = cpu_to_le32(value1);
    packet.field[2].value = cpu_to_le32(value2);

    packet.end.crc = 0x00;  /* Not used */
    packet.end.mark = ADV_PKT_END;

    /* Write packet */
    adv_write_buf((uint8_t *)&packet, sizeof(packet));
}

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

Извините, если это тривиальный вопрос. Я не программист, но мне нужно это решить, и я не смог найти решение (которое я могу понять!) после поиска в течение нескольких дней.

Функция чтения, которую я использую в Linux:

int serialport_read_until(int fd, unsigned char* buf, char until){
char b[1];
int i=0;
do { 
    int n = read(fd, b, 1);  // read a char at a time
    if( n==-1) return -1;    // couldn't read
    if( n==0 ) {
        usleep( 1 * 1000 ); // wait 1 msec try again
        continue;
    }
    buf[i] = b[0]; i++;
} while( b[0] != until );
buf[i] = 0;  // null terminate the string
return 0;}

Новая функция чтения:

    // Read the header part
adv_data_start_t start;
serial_read_buf(fd, reinterpret_cast<uint8_t*>(&start), sizeof(start));

// Create a buffer for the data and the end marker
std::vector<uint8_t> data_and_end(start.length - sizeof(start));

// Read the data and end marker
serial_read_buf(fd, data_and_end.data(), data_and_end.size());

// Iterate over the data
size_t num_data_fields = (data_and_end.size() - sizeof(adv_data_end_t)) / sizeof(adv_data_field_t);

adv_data_field_t* fields = reinterpret_cast<adv_data_field_t*>(data_and_end.data());


for (size_t i = 0; i < num_data_fields; i++)
    std::cout << "Field #" << (i + 1) << " = " << fields[i].value << '\n';

Пакеты данных, которые отправляются из прошивки:

typedef struct {
uint8_t         header1;        // header bytes - always 0xFF5A
uint8_t         header2;        // header bytes - always 0xFF5A
uint16_t        length;         // packet length (bytes)
uint32_t        time_stamp;     // time stamp (tick count)
    } adv_data_start_t;


typedef struct {
int32_t         value;          // data field value (3 VALUES)
} adv_data_field_t;


 typedef struct {
uint8_t         crc;            // 8-bit checksum
uint8_t         mark;           // 1-byte end-of-packet marker
uint16_t         mark2;           // 2-byte end-of-packet marker (Added to avoid data structure alignment problem)
 } adv_data_end_t;

Ответы:


1

Ну, у вас есть длина пакета в «заголовке» пакета, поэтому прочитайте поля заголовка (структура start) за одно чтение, а за второе чтение вы прочитаете данные и конец.

Если части start и end одинаковы для всех пакетов (что, я думаю, так и есть), вы можете легко определить количество полей данных после второго чтения.


Что-то вроде этого:

// Read the header part
adv_data_start_t start;
adv_read_buf(reinterpret_cast<uint8_t*>(&start), sizeof(start));

// Create a buffer for the data and the end marker
std::vector<uint8_t> data_and_end(start.length - sizeof(start));

// Read the data and end marker
adv_read_buf(data_and_end.data(), data_and_end.size());

// Iterate over the data
size_t num_data_fields = (data_and_end.size() - sizeof(adv_data_end_t)) / sizeof(adv_data_field_t);

adv_data_end_t* fields = reinterpret_cast<adv_data_end_t*>(data_and_end.data());

for (size_t i = 0; i < num_data_fields; i++)
    std::cout << "Field #" << (i + 1) << " = " << fields[i] << '\n';

Возможная read_buf реализация:

// Read `bufsize` bytes into `buffer` from a file descriptor
// Will block until `bufsize` bytes has been read
// Returns -1 on error, or `bufsize` on success
int serial_read_buf(int fd, uint8_t* buffer, const size_t bufsize)
{
    uint8_t* current = buffer;
    size_t remaining = bufsize

    while (remaining > 0)
    {
        ssize_t ret = read(fd, current, remaining);

        if (ret == -1)
            return -1;  // Error
        else if (ret == 0)
        {
            // Note: For some descriptors, this means end-of-file or
            //       connection closed.
            usleep(1000);
        }
        else
        {
            current += ret;  // Advance read-point in buffer
            remaining -= ret;  // Less data remaining to read
        }
    }

    return bufsize;
}
22.07.2013
  • Большое спасибо Иоахим. Дело в том, что я не уверен, как определить длину пакета в данных, я имею в виду, должен ли я читать байт за байтом и проверять, равен ли байт заголовку1, а затем предположить, что два байта после этого будут размером пакета? а тогда для чего "конец"? 23.07.2013
  • @Nico Необходимо всего два вызова чтения. Может быть больше, если возможно, что вызов чтения возвращает только частичные данные. Пожалуйста, смотрите пример кода в моем обновленном ответе. Что касается того, зачем нужна часть end, помните, что последовательная связь может быть ненадежной, и поэтому вам нужна некоторая проверка для этого (например, два поля заголовка, длина, (необязательно?) поле CRC и маркер конца пакета) . Вы должны проверить, что эти поля в порядке. 23.07.2013
  • Спасибо, Иоахим, это было действительно полезно! Хотя у меня проблемы с чтением порта. Функция adv_read_buf отсутствует и мне нужно написать ее самому. Функция, которую мы используем (я думаю), приводит к повреждению чтения половины пакетов данных, я добавил ее к основному вопросу. Он пытается прочитать пакет, пока не достигнет конечной метки \n, но, к сожалению, почти половина пакетов читается неправильно.... 25.07.2013
  • Ты серьезно мой герой! Теперь я могу читать поток данных, я бы никогда не подумал, что смогу это сделать, учитывая мои плохие знания в области программирования! Хотя есть новая проблема. время от времени вместо правильных значений я получаю ноль, а поле № вместо отображения от 1 до 3 (количество полей) переходит к очень большим числам. кажется, что опять же пакеты иногда не краснеют правильно. Мне нужно было немного изменить код, который вы написали, может быть, это из-за моих изменений? (я добавил код в вопрос) 26.07.2013
  • и иногда я получаю std::bad_alloc 26.07.2013
  • Новые материалы

    Учебные заметки JavaScript Object Oriented Labs
    Вот моя седьмая неделя обучения программированию. После ruby ​​и его фреймворка rails я начал изучать самый популярный язык интерфейса — javascript. В отличие от ruby, javascript — это более..

    Разбор строк запроса в vue.js
    Иногда вам нужно получить данные из строк запроса, в этой статье показано, как это сделать. В жизни каждого дизайнера/разработчика наступает момент, когда им необходимо беспрепятственно..

    Предсказание моей следующей любимой книги 📚 Благодаря данным Goodreads и машинному обучению 👨‍💻
    «Если вы не любите читать, значит, вы не нашли нужную книгу». - J.K. Роулинг Эта статья сильно отличается от тех, к которым вы, возможно, привыкли . Мне очень понравилось поработать над..

    Основы принципов S.O.L.I.D, Javascript, Git и NoSQL
    каковы принципы S.O.L.I.D? Принципы SOLID призваны помочь разработчикам создавать надежные, удобные в сопровождении приложения. мы видим пять ключевых принципов. Принципы SOLID были разработаны..

    Как настроить Selenium в проекте Angular
    Угловой | Селен Как настроить Selenium в проекте Angular Держите свое приложение Angular и тесты Selenium в одной рабочей области и запускайте их с помощью Mocha. В этой статье мы..

    Аргументы прогрессивного улучшения почти всегда упускают суть
    В наши дни в кругах веб-разработчиков много болтают о Progressive Enhancement — PE, но на самом деле почти все аргументы с обеих сторон упускают самую фундаментальную причину, по которой PE..

    Введение в Джанго Фреймворк
    Схема «работать умно, а не усердно» В этой и последующих статьях я познакомлю вас с тем, что такое фреймворк Django и как создать свое первое приложение с помощью простых и понятных шагов, а..