Отправка большого количества последовательных данных

13

Поэтому в области робототехники иногда требуется несколько плат и / или компьютеров, связанных друг с другом для обмена информацией или сохранения статистических данных. В настоящее время мне нужно отправить несколько различных переменных по последовательному соединению, и мне было интересно, как лучше всего это сделать?

До сих пор я решил, что отправка структур, вероятно, будет более простым способом отправки данных. Кто-нибудь знает какой-либо другой способ, который может быть более эффективным?

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

Steven10172
источник

Ответы:

9

С моими личными мыслями о том, что Structs является наиболее эффективным способом отправки множества различных переменных, я создал библиотеку, чтобы упростить отправку структур и переменных по последовательному каналу. Исходный код

В этой библиотеке это делает отправку через последовательный легко. Я использовал с серийным аппаратным и программным обеспечением. Обычно это используется в сочетании с 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()вам нужно знать, были ли данные ХОРОШИ, Не найдены или ПЛОХО.

Хорошо = Успешно

Not Found = Не найдено префиксного символа в указанном ostream

Плохо = Каким-то образом был найден префикс, но данные не повреждены. Обычно это означает, что не было найдено ни одного суффиксного символа или данные были неправильного размера.

Проверка достоверности данных:

// 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
Steven10172
источник
3
Ответы на все коды, как и ответы на все ссылки, не приветствуются. если в вашем коде нет тонны комментариев, я бы порекомендовал
дать
@TheDoctor, я обновил код. Теперь должно быть больше комментариев
Steven10172
1

Если вы действительно хотите отправить его быстро , я рекомендую Full Duplex Serial (FDX). Это тот же протокол, который используют USB и Ethernet, и он намного быстрее, чем UART. Недостатком является то, что обычно требуется внешнее оборудование для обеспечения высокой скорости передачи данных. Я слышал, что новый softwareSreial поддерживает FDX, но это может быть медленнее, чем аппаратный UART. Подробнее о протоколах связи см. Как подключить два Arduino без экранов?

Доктор
источник
Это звучит интересно. Я должен смотреть дальше в это.
Steven10172
Как « полнодуплексный последовательный » может быть «намного быстрее, чем UART», если это, по сути, стандартная связь UART?
Дэвид Кэри
UART - это связь с фиксированной скоростью. FDX отправляет данные как можно быстрее и отправляет данные, которые их не сделали.
TheDoctor
Я хотел бы узнать больше об этом протоколе. Не могли бы вы добавить в свой ответ ссылку, описывающую протокол, который работает быстрее, чем UART? Вы говорите об общей идее автоматического повторного запроса с использованием ACK-NAK , или вы имеете в виду какой-то конкретный протокол? Ни один из моих поисков в Google по запросу "FDX" или "full duplex serial" не соответствует вашему описанию.
Дэвид Кэри
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 при пинхай. Это произойдет практически в любой момент. если это нежелательно, удалите прерывание и просто наблюдайте за новыми персонажами в вашем главном исполнительном цикле (иначе, опрос 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 на другом конце, если машины различаются в порядке байтов.

Если важна целостность данных, вы также можете добавить контрольную сумму, чтобы убедиться, что вы не копируете неверно выровненные данные мусора. Это быстрая и эффективная проверка, которую я рекомендую.

80HD
источник
1

Если вы можете терпеть объем данных, отладки communicatons это так намного проще при передаче строк , чем при отправке бинарного; sprintf () / sscanf () и их варианты здесь ваши друзья. Заключите сообщение в выделенные функции в свой собственный модуль (файл .cpp); если вам нужно оптимизировать канал позже - после того, как у вас будет работающая система - вы можете заменить строковый модуль на один кодированный для меньших сообщений.

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

JRobert
источник
Первоначально код был написан для отправки данных в стабилизирующий цикл Quadcopter, поэтому он должен был быть довольно быстрым.
Steven10172
0

У меня нет официальных учетных данных, но, по моему опыту, все пошло довольно эффективно, когда я выбрал определенную позицию (и) символов, чтобы содержать состояние переменной, так что вы можете указать первые три символа как температуру, а следующие три как угол сервопривода и тд. На отправляющей стороне я сохранял переменные по отдельности, а затем объединял их в строку для последовательной отправки. На приемном конце я бы выделил строку, получив первые три символа и превратив их в нужный мне тип переменной, а затем сделал бы это снова, чтобы получить следующее значение переменной. Эта система работает лучше всего, когда вы точно знаете, сколько символов будет занимать каждая переменная, и вы всегда будете искать одни и те же переменные (которые, я надеюсь, являются заданными) каждый раз, когда последовательные данные проходят цикл.

Вы можете выбрать одну переменную, чтобы поместить последнюю переменную неопределенной длины, а затем получить эту переменную от ее первого символа до конца строки. Конечно, строка последовательных данных может быть очень длинной в зависимости от типов переменных и их огромного количества, но это система, которую я использую, и пока единственное препятствие, с которым я столкнулся, - это последовательная длина, так что это единственный недостаток, который я знать о.

Newbie97
источник
Какие функции вы используете для сохранения х символов в int / float / char?
Steven10172
1
Вы можете не осознавать этого, но то, что вы описываете, - это то, как именно а structорганизована в памяти (без учета заполнения), и я полагаю, что используемые вами функции передачи данных будут аналогичны тем, которые обсуждались в ответе Стивена .
asheeshr
@AsheeshR У меня действительно было чувство, что структуры могут быть такими, но я лично склонен врезаться в стену, когда пытаюсь переформатировать структуры, а затем прочитать их снова на другой стороне. Вот почему я решил, что я просто сделаю эту строковую вещь, чтобы я мог легко отлаживать, если что-то неправильно читалось, и чтобы я мог даже сам прочитать последовательные данные, если я назначу их как «MOTORa023 MOTORb563» и так далее, без пространства.
Newbie97
@ Steven10172 хорошо, я признаю, что не отслеживаю определенные функции, скорее, я каждый раз гугляю эту функцию. String to int, String для плавания и String для char . Имейте в виду, что я использую эти методы в обычном c ++ и сам не пробовал их в IDE Arduino.
Newbie97
0

Отправить данные структуры через последовательный

Ничего фантастического. Отправляет структуру. Он использует escape-символ «^» для разделения данных.

Arduino код

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
Гуру Субрамани
источник