Enviando grandes quantidades de dados seriais


13

Portanto, nos campos da robótica, às vezes você precisa de várias placas e / ou computadores conectados entre si para compartilhar informações ou salvar dados estatísticos. Atualmente, preciso enviar algumas variáveis ​​diferentes por uma conexão serial e fiquei imaginando qual seria a melhor maneira de fazer isso?

Até agora, determinei que o envio de estruturas provavelmente seria a maneira mais fácil de enviar dados. Alguém sabe de outra maneira que possa ser mais eficiente?

Lembre-se de que eu terei que enviar os dados para 4 motores, compressor, temperaturas diferentes, coisas aleatórias e as 3 seções do braço.

Respostas:


9

Com meus pensamentos pessoais sobre Structs sendo a maneira mais eficiente de enviar muitas variáveis ​​diferentes, construí uma biblioteca para ajudar a facilitar o envio de estruturas e variáveis ​​por serial. Código fonte

Nesta biblioteca, facilita o envio através de serial. Eu usei com serial de hardware e software. Normalmente, isso é usado em conjunto com o xbee's, para que eu possa enviar os dados sem fio de e para o robô.

Ao enviar dados, simplifique-o, pois permite enviar uma variável ou uma estrutura (não importa).

Aqui está um exemplo de envio de um char simples pela 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');

Exemplo de envio de um int simples pela 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');

Exemplo de envio de uma estrutura por serial:

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

Exemplos de recebimento:

Recebendo um char que foi enviado via 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');

Recebendo um int que foi enviado via 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');

Recebendo uma estrutura que foi enviada via 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');

Depois de ler os dados usando, StreamSend::receiveObject()você precisa saber se os dados foram BOM, Não encontrado ou RUIM.

Bom = bem sucedido

Não encontrado = Nenhum caractere de prefixo foi encontrado no ostream especificado

Ruim = De alguma forma, foi encontrado um caractere de prefixo, mas os dados não estão intactos. Normalmente, isso significa que não foi encontrado nenhum caractere de sufixo ou que os dados não tinham o tamanho correto.

Testando a validade dos dados:

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

Classe 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
Respostas com todos os códigos, como respostas com todos os links, são desencorajadas. a menos que o código tem de toneladas de comentários, eu recomendo colocar alguma explicação sobre o que está acontecendo
TheDoctor

@ TheDoctor, atualizei o código. Deve haver mais comentários agora
Steven10172 15/02

1

Se você realmente deseja enviá-lo rapidamente , recomendo o Full Duplex Serial (FDX). É o mesmo protocolo que USB e Ethernet usam, e é muito mais rápido que o UART. A desvantagem é que geralmente requer hardware externo para facilitar as altas taxas de dados. Ouvi dizer que o novo softwareSreial suporta FDX, mas isso pode ser mais lento até que o UART de hardware. Para saber mais sobre protocolos de comunicação, consulte Como conectar dois Arduino sem blindagens?


Isso parece interessante. Vou ter que investigar melhor.
Steven10172

Como o " full duplex serial " pode ser "muito mais rápido que o UART" quando é, de fato, a comunicação UART padrão?
David Cary

UART é uma comunicação de taxa fixa. O FDX envia os dados o mais rápido possível e reenvia os dados que não foram criados.
TheDoctor

Gostaria muito de saber mais sobre este protocolo. Você poderia adicionar um link à sua resposta que descreva um protocolo mais rápido que o UART? Você está falando da idéia geral de solicitação de repetição automática usando o ACK-NAK ou há algum protocolo específico em mente? Nenhuma das minhas pesquisas no Google por "FDX" ou "série full duplex" parece corresponder à sua descrição.
David Cary

1

Enviar uma estrutura é bastante simples.

Você pode declarar a estrutura como faria normalmente e, em seguida, usar o memcpy (@ myStruct, @ myArray) para copiar os dados para um novo local e, em seguida, usar algo semelhante ao código abaixo para gravar os dados como um fluxo de dados.

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 

Em seguida, você pode anexar uma rotina de interrupção ao pino no outro dispositivo que faz o seguinte:

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

attachInterrupt(0,readSerial,Rising);  

// diga ao mcu para chamar fxn quando estiver alto. Isso acontecerá praticamente a qualquer momento. se isso não for desejado, remova a interrupção e simplesmente observe novos personagens em seu loop executivo principal (também conhecido como pesquisa UART).

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

Sintaxe e uso de ponteiros precisarão de alguma revisão. Puxei a noite toda, então tenho certeza de que o código acima nem será compilado, mas a ideia está aí. Preencha sua estrutura, copie-a, use sinalização fora de banda para evitar erros de enquadramento, escreva os dados. Por outro lado, receba os dados, copie-os para uma estrutura e, em seguida, os dados se tornam acessíveis por métodos normais de acesso a membros.

O uso de campos de bits também funcionará, apenas lembre-se de que os petiscos parecerão invertidos. Por exemplo, tentar escrever 0011 1101, pode resultar em 1101 0011 aparecendo na outra extremidade, se as máquinas diferirem na ordem dos bytes.

Se a integridade dos dados for importante, você também poderá adicionar uma soma de verificação para garantir que não esteja copiando dados de lixo desalinhados. Esta é uma verificação rápida e eficaz que eu recomendo.


1

Se você pode tolerar o volume de dados, depuração Communicatons é assim muito mais fácil quando o envio de cordas do que quando o envio de binário; sprintf () / sscanf () e suas variantes são seus amigos aqui. Coloque a comunicação em funções dedicadas em seu próprio módulo (arquivo .cpp); se você precisar otimizar o canal posteriormente - depois de ter um sistema em funcionamento - poderá substituir o módulo baseado em string por um codificado para mensagens menores.

Você tornará sua vida muito mais fácil se você se apegar a especificações de protocolo na transmissão e interpretá-las de maneira mais vaga na recepção, considerando larguras de campos, delimitadores, terminações de linhas, zeros insignificantes, presença de +sinais etc.


Originalmente, o código foi escrito para enviar dados de volta em um loop estabilizador de um Quadcopter, por isso tinha que ser bastante rápido.
precisa saber é o seguinte

0

Não tenho credenciais oficiais aqui, mas, na minha experiência, as coisas foram bem eficientes quando escolho uma (s) determinada (s) posição (s) de caractere para conter o estado de uma variável, para que você possa designar os três primeiros caracteres como temperatura e os próximos três como o ângulo de um servo, e assim por diante. No final do envio, eu salvava as variáveis ​​individualmente e as combinava em uma string para enviar serialmente. No lado de recebimento, eu teria a string separada, obtendo os três primeiros caracteres e transformando-os em qualquer tipo de variável que eu precisasse, e fazendo isso novamente para obter o próximo valor de variável. Esse sistema funciona melhor quando você sabe ao certo a quantidade de caracteres que cada variável terá, e você sempre procura as mesmas variáveis ​​(que eu espero que sejam um dado) toda vez que os dados seriais circulam.

Você pode escolher uma variável para colocar o último de comprimento indeterminado e depois obtê-la do seu primeiro caractere até o final da string. É verdade que a cadeia de dados seriais pode ficar muito longa, dependendo dos tipos de variáveis ​​e da quantidade total deles, mas esse é o sistema que eu uso e, até agora, o único revés que atingi é o comprimento de série, portanto, é a única desvantagem que eu saber de.


Que tipo de funções você usa para salvar x quantidade de caracteres em um int / float / char?
Steven10172

1
Você pode não perceber isso, mas o que você descreve é exatamente como a structé organizado na memória (desconsiderando o preenchimento) e imagino que as funções de transferência de dados que você usa sejam semelhantes às discutidas na resposta de Steven .
asheeshr

@AsheeshR Na verdade, eu tinha a sensação de que as estruturas podem ser assim, mas eu pessoalmente tendem a bater em uma parede ao tentar reformatar as estruturas e depois lê-las novamente do outro lado. É por isso que eu pensei em fazer essa coisa de string, para poder depurar facilmente se as coisas forem mal interpretadas, e para que eu possa ler os dados seriais se designar como "MOTORa023 MOTORb563" e assim por diante, sem os espaços.
Newbie97

@ Steven10172 bem, admito que não acompanho as funções específicas, mas pesquiso a função em particular a cada vez. String para int, String para flutuar e String para char . Lembre-se de que eu uso esses métodos em c ++ regular e não os experimentei no IDE do Arduino.
Newbie97

0

Enviar dados de estrutura através de serial

Nada chique. Envia uma estrutura. Ele usa um caractere de escape '^' para delimitar os dados.

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 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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.