Надсилання великої кількості послідовних даних


13

Тож у галузях робототехніки іноді потрібні кілька плат та комп'ютерів, пов'язаних між собою для обміну інформацією або збереження статистичних даних. В даний час мені потрібно надіслати кілька різних змінних через послідовне з'єднання, і мені було цікаво, який найкращий спосіб зробити таке?

Поки я визначив, що надсилання структур, ймовірно, буде найпростішим способом надсилання даних. Хтось знає про будь-який інший спосіб, який може бути більш ефективним?

Будь ласка, майте на увазі, що мені, по суті, доведеться надсилати дані для 4 двигунів, компресора, різних температур, випадкових речей та 3 секцій руки.

Відповіді:


9

З моїми особистими думками щодо "Структури" є найбільш ефективним способом надсилання багатьох різних змінних, я створив бібліотеку, щоб полегшити надсилання структур та змінних за серійними. Вихідний код

У цій бібліотеці це дозволяє легко надсилати через серійні програми. Я працював із серійними апаратними та програмними засобами. Зазвичай це використовується спільно з xbee, тому я можу бездротово надсилати дані до робота та від нього.

При надсиланні даних це спрощує, оскільки дозволяє надіслати змінну або структуру (це не хвилює).

Ось приклад надсилання простої картки через серійний:

// Send the variable charVariable over the serial.
// To send the variable you need to pass an instance of the Serial to use,
// a reference to the variable to send, and the size of the variable being sent.
// If you would like you can specify 2 extra arguments at the end which change the
// default prefix and suffix character used when attempting to reconstruct the variable
// on the receiving end. If prefix and suffix character are specified they'll need to 
// match on the receiving end otherwise data won't properly be sent across

char charVariable = 'c'; // Define the variable to be sent over the serial
StreamSend::sendObject(Serial, &charVariable, sizeof(charVariable));

// Specify a prefix and suffix character
StreamSend::sendObject(Serial, &charVariable, sizeof(charVariable), 'a', 'z');

Приклад надсилання простого int через серійний:

int intVariable = 13496; // Define the int to be sent over the serial
StreamSend::sendObject(xbeeSerial, &intVariable, sizeof(intVariable));

// Specify a prefix and suffix character
StreamSend::sendObject(xbeeSerial, &intVariable, sizeof(intVariable), 'j', 'p');

Приклад надсилання структури через серійний:

// Define the struct to be sent over the serial
struct SIMPLE_STRUCT {
  char charVariable;
  int intVariable[7];
  boolean boolVariable;
};
SIMPLE_STRUCT simpleStruct;
simpleStruct.charVariable = 'z'; // Set the charVariable in the struct to z

// Fill the intVariable array in the struct with numbers 0 through 6
for(int i=0; i<7; i++) {
  simpleStruct.intVariable[i] = i;
}

// Send the struct to the object xbeeSerial which is a software serial that was
// defined. Instead of using xbeeSerial you can use Serial which will imply the
// hardware serial, and on a Mega you can specify Serial, Serial1, Serial2, Serial3.
StreamSend::sendObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct));

// Send the same as above with a different prefix and suffix from the default values
// defined in StreamSend. When specifying when prefix and suffix character to send
// you need to make sure that on the receiving end they match otherwise the data
// won't be able to be read on the other end.
StreamSend::sendObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct), '3', 'u');

Приклади отримання:

Отримання картки, надісланої через Streamsend:

char charVariable; // Define the variable on where the data will be put

// Read the data from the Serial object an save it into charVariable once
// the data has been received
byte packetResults = StreamSend::receiveObject(Serial, &charVariable, sizeof(charVariable));

// Reconstruct the char coming from the Serial into charVariable that has a custom
// suffix of a and a prefix of z
byte packetResults = StreamSend::receiveObject(Serial, &charVariable, sizeof(charVariable), 'a', 'z');

Отримання int, надісланого через StreamSend:

int intVariable; // Define the variable on where the data will be put

// Reconstruct the int from xbeeSerial into the variable intVariable
byte packetResults = StreamSend::receiveObject(xbeeSerial, &intVariable, sizeof(intVariable));

// Reconstruct the data into intVariable that was send with a custom prefix
// of j and a suffix of p
byte packetResults = StreamSend::receiveObject(xbeeSerial, &intVariable, sizeof(intVariable), 'j', 'p');

Отримання структури, надісланої через StreamSend:

// Define the struct that the data will be put
struct SIMPLE_STRUCT {
  char charVariable;
  int intVariable[7];
  boolean boolVariable;
};
SIMPLE_STRUCT simpleStruct; // Create a struct to store the data in

// Reconstruct the data from xbeeSerial into the object simpleStruct
byte packetResults = StreamSend::receiveObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct));

// Reconstruct the data from xbeeSerial into the object simplestruct that has
// a prefix of 3 and a suffix of p
byte packetResults = StreamSend::receiveObject(xbeeSerial, &simpleStruct, sizeof(simpleStruct), '3', 'p');

Після того, як ви читаєте дані, використовуючи їх, StreamSend::receiveObject()вам потрібно знати, чи були дані ДОБРИМИ, НЕ знайдені або БУДІ

Хороший = Успішний

Не знайдено = У вказаному потоці не знайдено символу префікса

Погано = Якось знайдено символ префікса, але дані не є цілими. Зазвичай це означає, що символу суфікса не знайдено або дані не були правильного розміру.

Тестування достовірності даних:

// Once you call StreamSend::receiveObject() it returns a byte of the status of
// how things went. If you run that though some of the testing functions it'll
// let you know how the transaction went
if(StreamSend::isPacketGood(packetResults)) {
  //The Packet was Good
} else {
  //The Packet was Bad
}

if(StreamSend::isPacketCorrupt(packetResults)) {
  //The Packet was Corrupt
} else {
  //The Packet wasn't found or it was Good
}

if(StreamSend::isPacketNotFound(packetResults)) {
  //The Packet was not found after Max # of Tries
} else {
  //The Packet was Found, but can be corrupt
}

Клас SteamSend:

#include "Arduino.h"

#ifndef STREAMSEND_H
#define STREAMSEND_H


#define PACKET_NOT_FOUND 0
#define BAD_PACKET 1
#define GOOD_PACKET 2

// Set the Max size of the Serial Buffer or the amount of data you want to send+2
// You need to add 2 to allow the prefix and suffix character space to send.
#define MAX_SIZE 64


class StreamSend {
  private:
    static int getWrapperSize() { return sizeof(char)*2; }
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize, unsigned int loopSize);
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize, unsigned int loopSize, char prefixChar, char suffixChar);
    static char _prefixChar; // Default value is s
    static char _suffixChar; // Default value is e
    static int _maxLoopsToWait;

  public:
    static void sendObject(Stream &ostream, void* ptr, unsigned int objSize);
    static void sendObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar);
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize);
    static byte receiveObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar);
    static boolean isPacketNotFound(const byte packetStatus);
    static boolean isPacketCorrupt(const byte packetStatus);
    static boolean isPacketGood(const byte packetStatus);

    static void setPrefixChar(const char value) { _prefixChar = value; }
    static void setSuffixChar(const char value) { _suffixChar = value; }
    static void setMaxLoopsToWait(const int value) { _maxLoopsToWait = value; }
    static const char getPrefixChar() { return _prefixChar; }
    static const char getSuffixChar() { return _suffixChar; }
    static const int getMaxLoopsToWait() { return _maxLoopsToWait; }

};

//Preset Some Default Variables
//Can be modified when seen fit
char StreamSend::_prefixChar = 's';   // Starting Character before sending any data across the Serial
char StreamSend::_suffixChar = 'e';   // Ending character after all the data is sent
int StreamSend::_maxLoopsToWait = -1; //Set to -1 for size of current Object and wrapper



/**
  * sendObject
  *
  * Converts the Object to bytes and sends it to the stream
  *
  * @param Stream to send data to
  * @param ptr to struct to fill
  * @param size of struct
  * @param character to send before the data stream (optional)
  * @param character to send after the data stream (optional)
  */
void StreamSend::sendObject(Stream &ostream, void* ptr, unsigned int objSize) {
  sendObject(ostream, ptr, objSize, _prefixChar, _suffixChar);
}

void StreamSend::sendObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar) {
  if(MAX_SIZE >= objSize+getWrapperSize()) { //make sure the object isn't too large
    byte * b = (byte *) ptr; // Create a ptr array of the bytes to send
    ostream.write((byte)prefixChar); // Write the suffix character to signify the start of a stream

    // Loop through all the bytes being send and write them to the stream
    for(unsigned int i = 0; i<objSize; i++) {
      ostream.write(b[i]); // Write each byte to the stream
    }
    ostream.write((byte)suffixChar); // Write the prefix character to signify the end of a stream
  }
}

/**
  * receiveObject
  *
  * Gets the data from the stream and stores to supplied object
  *
  * @param Stream to read data from
  * @param ptr to struct to fill
  * @param size of struct
  * @param character to send before the data stream (optional)
  * @param character to send after the data stream (optional)
  */
byte StreamSend::receiveObject(Stream &ostream, void* ptr, unsigned int objSize) {
    return receiveObject(ostream, ptr, objSize, _prefixChar, _suffixChar);
}
byte StreamSend::receiveObject(Stream &ostream, void* ptr, unsigned int objSize, char prefixChar, char suffixChar) {
  return receiveObject(ostream, ptr, objSize, 0, prefixChar, suffixChar);
}

byte StreamSend::receiveObject(Stream &ostream, void* ptr, unsigned int objSize, unsigned int loopSize, char prefixChar, char suffixChar) {
  int maxLoops = (_maxLoopsToWait == -1) ? (objSize+getWrapperSize()) : _maxLoopsToWait;
  if(loopSize >= maxLoops) {
      return PACKET_NOT_FOUND;
  }
  if(ostream.available() >= (objSize+getWrapperSize())) { // Packet meets minimum size requirement
    if(ostream.read() != (byte)prefixChar) {
      // Prefix character is not found
      // Loop through the code again reading the next char
      return receiveObject(ostream, ptr, objSize, loopSize+1, prefixChar, suffixChar);
    }

    char data[objSize]; //Create a tmp char array of the data from Stream
    ostream.readBytes(data, objSize); //Read the # of bytes
    memcpy(ptr, data, objSize); //Copy the bytes into the struct

    if(ostream.read() != (byte)suffixChar) {
      //Suffix character is not found
      return BAD_PACKET;
    }
      return GOOD_PACKET;
  }
  return PACKET_NOT_FOUND; //Prefix character wasn't found so no packet detected
}


boolean StreamSend::isPacketNotFound(const byte packetStatus) {
    return (packetStatus == PACKET_NOT_FOUND);
}

boolean StreamSend::isPacketCorrupt(const byte packetStatus) {
    return (packetStatus == BAD_PACKET);
}

boolean StreamSend::isPacketGood(const byte packetStatus) {
    return (packetStatus == GOOD_PACKET);
}

#endif

3
Відповіді на всі коди, як і відповіді на всі посилання, не відмовляються. якщо у вашому коді немає тонких коментарів, я б рекомендував розмістити пояснення того, що відбувається
TheDoctor

@TheDoctor, я оновив код. Зараз має бути більше коментарів
Steven10172

1

Якщо ви дійсно хотіли відправити його швидко , я рекомендую повний дуплексний серійний (FDX). Це той самий протокол, який використовують USB та Ethernet, і це набагато швидше, ніж UART. Мінусом є те, що для забезпечення високої швидкості передачі даних зазвичай потрібне зовнішнє обладнання. Я чув, що новий softwareSreial підтримує FDX, але це може бути повільніше, ніж апаратний UART. Детальніше про протоколи зв’язку див. У розділі Як з'єднати два Arduino без екранів?


Це звучить цікаво. Мені доведеться заглянути далі.
Steven10172

Як " повний дуплексний серій " може бути "набагато швидшим, ніж UART", коли це, власне, стандартне спілкування UART?
Девід Кері

UART - комунікація з фіксованою швидкістю. FDX надсилає дані якомога швидше і повторно відправляє дані, які не вносили їх.
TheDoctor

Я хотів би дізнатися більше про цей протокол. Чи можете ви додати посилання на свою відповідь, що описує протокол, який швидше, ніж UART? Ви говорите про загальну ідею автоматичного повторного запиту за допомогою ACK-NAK , чи є якийсь конкретний протокол, який ви маєте на увазі? Жоден з моїх пошукових запитів Google "FDX" або "повний дуплексний серіал" не відповідає вашому опису.
Девід Кері

1

Надіслати структуру досить просто.

Ви можете оголосити структуру як зазвичай, а потім скористатися memcpy (@ myStruct, @ myArray), щоб скопіювати дані в нове місце, а потім використовувати щось подібне до наведеного нижче коду для запису даних як потоку даних.

unsigned char myArraySender[##];   //make ## large enough to fit struct
memcpy(&myStruct,&myArraySender);  //copy raw data from struct to the temp array
digitalWrite(frameStartPin,High);  //indicate to receiver that data is coming
serial.write(sizeof myStruct);     //tell receiver how many bytes to rx
Serial.write(&myArraySender,sizeof myStruct);   //write bytes
digitalWrite)frameStartPin,Low);   //done indicating transmission 

Потім ви можете приєднати процедуру переривання до шпильки на іншому пристрої, яка виконує такі дії:

volatile unsigned char len, tempBuff[##];   
//volatile because the interrupt will not happen at predictable intervals.

attachInterrupt(0,readSerial,Rising);  

// скажіть mcu викликати fxn, коли pinhigh. Це станеться практично в будь-який момент. якщо цього не потрібно, видаліть переривання та просто слідкуйте за новими символами у вашому головному циклі виконавців (він же, опитування UART).

void readSerial(unsigned char *myArrayReceiver){
    unsigned char tempbuff[sizeof myArrayReceiver];
    while (i<(sizeof myArrayReceiver)) tempBuff[i]=Serial.read();
    memcpy(&tempbuff,&myArrayReceiver);
    Serial.flush();
}

Синтаксис та використання покажчиків потребує певного огляду. Я витягнув все більш сильний, тому впевнений, що вищезгаданий код навіть не складеться, але ідея є. Заповніть свою структуру, скопіюйте її, використовуйте позадіапазонну сигналізацію, щоб уникнути помилок обрамлення, запишіть дані. З іншого боку, отримайте дані, скопіюйте їх у структуру, а потім дані стануть доступними за допомогою звичайних методів доступу для членів.

Використання бітових полів також спрацює, лише майте на увазі, що грибки будуть здаватися назад. Наприклад, спроба записати 0011 1101 може призвести до того, що 1101 0011 з'явиться на іншому кінці, якщо машини відрізняються в порядку байтів.

Якщо цілісність даних важлива, ви також можете додати контрольну суму, щоб переконатися, що ви не копіюєте несогласовані дані сміття. Це швидка та ефективна перевірка, яку я рекомендую.


1

Якщо ви можете терпіти обсяг даних, налагодження communicatons це так набагато простіше при передачі рядків , ніж при відправці бінарного; sprintf () / sscanf () та їх варіанти тут є вашими друзями. Включити зв’язок у виділені функції у власному модулі (.cpp файл); якщо вам потрібно буде оптимізувати канал пізніше - після того, як у вас є робоча система - ви можете замінити рядовий модуль одним кодованим для менших повідомлень.

Ви полегшите своє життя, якщо будете чітко дотримуватися специфікацій протоколу при передачі та інтерпретувати їх більш вільно під час прийому, враховуючи ширину поля, роздільники, закінчення рядків, незначні нулі, наявність +знаків тощо.


Спочатку код був написаний для того, щоб відправляти дані назад в стабілізуючий цикл Quadcopter, тому він повинен був бути досить швидким.
Стівен10172

0

Я не маю тут офіційних даних, але, на моєму досвіді, справи пішли досить ефективно, коли я вибираю певну позицію символів, щоб містити стан змінної, щоб ви могли позначити перші три символи як температуру, а наступну три як кут сервоприводу тощо. На кінці відправлення я б зберігав змінні окремо, а потім поєднувати їх у рядок для надсилання послідовно. На кінці прийому я мав би виділити рядок, розібравши перші три символи та перетворивши їх у будь-який тип змінної, який мені потрібен, а потім зробити це ще раз, щоб отримати наступне значення змінної. Ця система найкраще працює, коли ви знаєте, напевно, кількість символів, яка займає кожна змінна, і ви завжди шукаєте однакові змінні (які, я сподіваюся, є даними) щоразу, коли послідовний цикл даних проходить.

Ви можете вибрати одну змінну для останнього невизначеної довжини, а потім отримати цю змінну від її першого символу до кінця рядка. Зрозуміло, що послідовний рядок даних може отримати дуже довгий, залежно від типів змінних та великої кількості їх, але це система, яку я використовую, і поки що єдиним невдачею, на який я потрапив, є серіальна довжина, тому це єдиний недолік знати.


Які функції ви використовуєте для збереження x кількості символів у int / float / char?
Steven10172

1
Ви можете цього не усвідомлювати, але те, що ви описуєте, саме те , як structорганізовується пам'ять (ігнорування прокладки), і я думаю, що функції передачі даних, які ви використовуєте, будуть аналогічні тим, які обговорювалися у відповіді Стівена .
asheeshr

@AsheeshR У мене справді було відчуття, що це може бути саме так, але я особисто схильний вдарити стіну при спробі переформатувати структури, а потім прочитати їх знову з іншого боку. Ось чому я подумав, що я просто займусь цією строковою справою, щоб я міг легко налагоджувати, якщо щось неправильно читати, і щоб я міг навіть сам прочитати серійні дані, якщо позначу це як "MOTORa023 MOTORb563" і так далі, без пробіли.
Newbie97

@ Steven10172 добре, я визнаю, що я не відслідковую конкретні функції, а кожен раз переглядаю цю функцію в Google. String to int, String to float та String to char . Майте на увазі, що я використовую ці методи у звичайному режимі c ++ і сам не пробував їх у програмі Arduino IDE.
Newbie97

0

Надсилайте структурні дані через серійні

Нічого фантазійного. Надсилає структуру. Для розмежування даних для символу використовується символ "^" для відходу.

Код Ардуїно

typedef struct {
 float ax1;
 float ay1;
 float az1;
 float gx1;
 float gy1;
 float gz1;
 float ax2;
 float ay2;
 float az2;
 float gx2;
 float gy2;
 float gz2;

} __attribute__((__packed__))data_packet_t;

data_packet_t dp;

template <typename T> void sendData(T data)
{
 unsigned long uBufSize = sizeof(data);
 char pBuffer[uBufSize];

 memcpy(pBuffer, &dp, uBufSize);
 Serial.write('^');
 for(int i = 0; i<uBufSize;i++) {
   if(pBuffer[i] == '^')
   {
    Serial.write('^');
    }
   Serial.write(pBuffer[i]);
 }
}
void setup() {
  Serial.begin(57600);
}
void loop(){
dp.ax1 = 0.03; // Note that I didn't fill in the others. Too much work. ;p
sendData<data_packet_t>(dp);
}

Код Python:

import serial
from  copy import copy
from struct import *


ser = serial.Serial(
#   port='/dev/cu.usbmodem1412',
  port='/dev/ttyUSB0',
#     port='/dev/cu.usbserial-AL034MCJ',
    baudrate=57600
)



def get_next_data_block(next_f):
    if not hasattr(get_next_data_block, "data_block"):
        get_next_data_block.data_block = []
    while (1):
        try:
            current_item = next_f()
            if current_item == '^':
                next_item = next_f()
                if next_item == '^':
                    get_next_data_block.data_block.append(next_item)
                else:
                    out = copy(get_next_data_block.data_block)
                    get_next_data_block.data_block = []
                    get_next_data_block.data_block.append(next_item)
                    return out
            else:
                get_next_data_block.data_block.append(current_item)
        except :
            break


for i in range(1000): # just so that the program ends - could be in a while loop
    data_ =  get_next_data_block(ser.read)
    try:
        print unpack('=ffffffffffff', ''.join(data_))
    except:
        continue
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.