Envío de grandes cantidades de datos en serie

13

Por lo tanto, en el campo de la robótica, a veces se necesitan varias placas o computadoras conectadas para compartir información o guardar datos estadísticos. Actualmente necesito enviar algunas variables diferentes a través de una conexión en serie y me preguntaba cuál era la mejor manera de hacerlo.

Hasta ahora he determinado que enviar estructuras probablemente sea la forma más fácil de enviar datos. ¿Alguien sabe de alguna otra manera que podría ser más eficiente?

Tenga en cuenta que esencialmente tendré que enviar los datos de 4 motores, compresores, diferentes temperaturas, elementos aleatorios y las 3 secciones del brazo.

Steven10172
fuente

Respuestas:

9

Con mis pensamientos personales sobre Structs como la forma más eficiente de enviar muchas variables diferentes, he construido una biblioteca para ayudar a facilitar el envío de estructuras y variables en serie. Código fuente

En esta biblioteca, hace que el envío sea en serie fácilmente. He usado con hardware y software en serie. Por lo general, esto se usa en conjunto con xbee para que pueda enviar de forma inalámbrica los datos hacia y desde el robot.

Cuando envíe datos, hágalo simple, ya que le permite enviar una variable o una estructura (no le importa).

Aquí hay un ejemplo de envío de un simple char sobre el serial:

// 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');

Ejemplo de enviar un int simple sobre el serial:

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');

Ejemplo de envío de una estructura en serie:

// 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');

Recibiendo ejemplos:

Al recibir un char que se envió a través de 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');

Al recibir un int que se envió a través de 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');

Al recibir un Struct que se envió a través de 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');

Una vez que lea los datos utilizando StreamSend::receiveObject(), necesita saber si los datos fueron BUENOS, No encontrados o MALOS.

Bueno = exitoso

No encontrado = No se encontró ningún carácter de prefijo en el ostream especificado

Malo = De alguna manera se encontró un carácter de prefijo, pero los datos no están intactos. Por lo general, significa que no se encontraron caracteres de sufijo o que los datos no tenían el tamaño correcto.

Validez de prueba de datos:

// 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
}

Clase 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
fuente
3
Se desaconsejan las respuestas a todos los códigos, como las respuestas a todos los enlaces. a menos que su código tenga toneladas de comentarios, recomendaría dar una explicación de lo que está sucediendo
TheDoctor
@TheDoctor, he actualizado el código. Debería haber más comentarios ahora
Steven10172
1

Si realmente desea enviarlo rápido , le recomiendo la serie Full Duplex (FDX). Es el mismo protocolo que utilizan USB y Ethernet, y es mucho más rápido que UART. La desventaja es que generalmente requiere hardware externo para facilitar las altas velocidades de datos. He oído que el nuevo softwareSreial es compatible con FDX, pero esto puede ser más lento incluso que el hardware UART. Para obtener más información sobre los protocolos de comunicación, consulte ¿Cómo conectar dos Arduino sin escudos?

TheDoctor
fuente
Esto suena interesante Tendré que investigar más a fondo.
Steven10172
¿Cómo puede ser que " serial full duplex " sea "mucho más rápido que UART" cuando es, de hecho, comunicación UART estándar?
David Cary
UART es una comunicación de velocidad fija. FDX envía datos lo más rápido posible y reenvía los datos que no lo hicieron.
TheDoctor
Me encantaría saber más sobre este protocolo. ¿Podría agregar un enlace a su respuesta que describa un protocolo que sea más rápido que UART? ¿Está hablando de la idea general de la solicitud de repetición automática utilizando ACK-NAK , o hay algún protocolo específico que tenga en mente? Ninguna de mis búsquedas de Google para "FDX" o "full duplex serial" parece coincidir con su descripción.
David Cary
1

Enviar una estructura es bastante simple.

Puede declarar la estructura como lo haría normalmente, y luego usar memcpy (@ myStruct, @ myArray) para copiar los datos en una nueva ubicación, y luego usar algo similar al código siguiente para escribir los datos como flujo de datos.

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 

Luego puede adjuntar una rutina de interrupción al pin en el otro dispositivo que hace lo siguiente:

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

attachInterrupt(0,readSerial,Rising);  

// dile a mcu que llame a fxn cuando pinhigh. Esto sucederá en prácticamente cualquier momento. si eso no se desea, elimine la interrupción y simplemente busque nuevos personajes en su bucle ejecutivo principal (también conocido como sondeo UART).

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

La sintaxis y el uso de punteros necesitarán alguna revisión. Estuve toda la noche, así que estoy seguro de que el código anterior ni siquiera se compilará, pero la idea está ahí. Rellene su estructura, cópiela, utilice la señalización fuera de banda para evitar errores de trama, escriba los datos. En el otro extremo, reciba los datos, cópielos en una estructura y luego los datos serán accesibles a través de métodos normales de acceso de miembros.

El uso de bitfields también funcionará, solo tenga en cuenta que los mordiscos parecerán estar al revés. Por ejemplo, al intentar escribir 0011 1101, puede aparecer 1101 0011 en el otro extremo si las máquinas difieren en orden de bytes.

Si la integridad de los datos es importante, también puede agregar una suma de verificación para asegurarse de que no está copiando datos basura desalineados. Este es un control rápido y efectivo que recomiendo.

80HD
fuente
1

Si usted puede tolerar el volumen de datos, depuración Communicatons es por lo tanto más fácil al enviar cadenas que al enviar binaria; sprintf () / sscanf () y sus variantes son tus amigos aquí. Adjunte la comunicación en funciones dedicadas en su propio módulo (archivo .cpp); si necesita optimizar el canal más adelante, después de tener un sistema en funcionamiento, puede reemplazar el módulo basado en cadenas por uno codificado para mensajes más pequeños.

Hará su vida mucho más fácil si se atiene a las especificaciones de protocolo en la transmisión e interpreta más libremente en la recepción, en relación con los anchos de campo, delimitadores, terminaciones de línea, ceros insignificantes, presencia de +signos, etc.

JRobert
fuente
Originalmente, el código se escribió para enviar datos en un bucle estabilizador de un Quadcopter, por lo que tenía que ser bastante rápido.
Steven10172
0

No tengo credenciales oficiales aquí, pero según mi experiencia, las cosas se han vuelto bastante eficientes cuando elijo ciertas posiciones de caracteres para contener el estado de una variable, de modo que pueda designar los primeros tres caracteres como la temperatura, y el siguiente tres como el ángulo de un servo, y así sucesivamente. En el extremo de envío, guardaría las variables individualmente y luego las combinaría en una cadena para enviar en serie. En el extremo de recepción, separaría la cadena, obtendría los primeros tres caracteres y los convertiría en cualquier tipo de variable que necesite, y luego lo haría nuevamente para obtener el siguiente valor de variable. Este sistema funciona mejor cuando se sabe con certeza la cantidad de caracteres que ocupará cada variable, y siempre se buscan las mismas variables (lo cual espero sea un hecho) cada vez que los datos en serie se repiten.

Puede elegir una variable para colocar la última de longitud indeterminada y luego obtener esa variable desde su primer carácter hasta el final de la cadena. De acuerdo, la cadena de datos en serie podría ser muy larga dependiendo de los tipos de variables y la gran cantidad de ellos, pero este es el sistema que uso y hasta ahora el único inconveniente que he encontrado es la longitud de serie, por lo que esa es la única desventaja que tengo. saber de.

Newbie97
fuente
¿Qué tipo de funciones utiliza para guardar x cantidad de caracteres en un int / float / char?
Steven10172
1
Puede que no se dé cuenta de esto, pero lo que describe es exactamente cómo structse organiza a en la memoria (sin tener en cuenta el relleno) e imagino que las funciones de transferencia de datos que usa serán similares a las que se analizan en la respuesta de Steven .
asheeshr
@AsheeshR Realmente tuve la sensación de que las estructuras podrían ser de esa manera, pero personalmente tiendo a golpear una pared cuando intento reformatear estructuras y luego leerlas nuevamente en el otro lado. Es por eso que pensé que simplemente haría esta cosa de cadena, para poder depurar fácilmente si las cosas se malinterpretan, y para poder incluso leer los datos en serie yo mismo si los designo como "MOTORa023 MOTORb563" y así sucesivamente, sin Los espacios.
Novato97
@ Steven10172 Bueno, admito que no hago un seguimiento de las funciones específicas, sino que busco cada función en Google cada vez. Cadena a int, Cadena a flotar y Cadena a char . Tenga en cuenta que uso estos métodos en c ++ normal y no los he probado en el IDE de Arduino.
Newbie97
0

Enviar datos de estructura a través de serie

Nada sofisticado. Envía una estructura. Utiliza un carácter de escape '^' para delimitar los datos.

Código 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);
}

Código de 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
Guru Subramani
fuente