대량의 시리얼 데이터 전송


13

따라서 로봇 공학 분야에서는 정보를 공유하거나 통계 데이터를 저장하기 위해 서로 연결된 여러 보드 또는 컴퓨터가 필요할 때가 있습니다. 현재 직렬 연결을 통해 몇 가지 다른 변수를 보내야하며 그러한 작업을 수행하는 가장 좋은 방법이 무엇인지 궁금하십니까?

지금까지 구조를 보내는 것이 데이터를 보내는 것이 더 쉬운 방법이라고 생각했습니다. 더 효율적인 다른 방법을 아는 사람이 있습니까?

본질적으로 4 모터, 압축기, 다른 온도, 임의의 물건 및 팔의 3 섹션에 대한 데이터를 보내야한다는 것을 명심하십시오.

답변:


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

StreamSend를 통해 보낸 int 받기 :

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를 통해 전송 된 Struct 받기 :

// 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()는 데이터가 GOOD, Not Found 또는 BAD인지 알아야합니다.

양호 = 성공

찾을 수 없음 = 지정된 ostream에 접두사 문자가 없습니다.

Bad = 어떻게 든 접두사 문자가 발견되었지만 데이터는 손상되지 않았습니다. 일반적으로 접미사 문자가 없거나 데이터의 크기가 잘못되었음을 의미합니다.

데이터의 유효성 검사 :

// 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 (Full Duplex Serial)를 권장합니다. USB 및 이더넷과 동일한 프로토콜이며 UART보다 훨씬 빠릅니다. 단점은 일반적으로 높은 데이터 속도를 위해 외부 하드웨어가 필요하다는 것입니다. 새 softwareSreial 이 FDX를 지원 한다고 들었지만 하드웨어 UART보다 속도가 느릴 수 있습니다. 통신 프로토콜에 대한 자세한 내용은 차폐없이 두 개의 Arduino를 연결하는 방법을 참조하십시오 .


흥미로운 소리입니다. 더 자세히 살펴 봐야합니다.
Steven10172

어떻게 "할 수 전이중 직렬 "는 사실, 표준 UART 통신에있을 때 "훨씬 빨리 UART보다 더"가?
David Cary

UART는 고정 요금 통신입니다. FDX는 가능한 빨리 데이터를 전송하고이를 만들지 않은 데이터를 다시 보냅니다.
TheDoctor

이 프로토콜에 대해 더 알고 싶습니다. UART보다 빠른 프로토콜을 설명하는 링크를 답변에 추가 할 수 있습니까? ACK-NAK를 사용 하는 자동 반복 요청 의 일반적인 아이디어에 대해 이야기하고 있습니까 , 아니면 특정 프로토콜이 있습니까? "FDX"또는 "전이중 시리얼"에 대한 Google 검색에서 귀하의 설명과 일치하는 것으로 보이지 않습니다.
David Cary

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

// pinhigh 일 때 fxn을 호출하도록 MCU에 알리십시오. 이것은 거의 모든 순간에 발생합니다. 필요하지 않은 경우 인터럽트를 제거하고 주 실행 루프에서 새로운 문자 (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의 안정화 루프에서 데이터를 다시 보내도록 작성되었으므로 상당히 빨랐습니다.
Steven10172

0

여기에는 공식 자격 증명이 없지만 경험상 변수의 상태를 포함 할 특정 문자 위치를 선택하면 첫 세 문자를 온도로 지정하고 다음 문자를 온도로 지정할 수 있습니다. 서보 각도로 ​​3 등입니다. 보내는 쪽에서 변수를 개별적으로 저장 한 다음 문자열로 결합하여 직렬로 보냅니다. 수신 측에서 문자열을 분리하여 처음 세 문자를 가져 와서 필요한 변수 유형으로 변환 한 다음 다시 다음 변수 값을 가져옵니다. 이 시스템은 각 변수가 차지할 문자의 양을 알고있을 때 가장 잘 작동하며 직렬 데이터가 반복 될 때마다 항상 동일한 변수 (원하는 경우)를 찾습니다.

하나의 변수를 선택하여 마지막 길이를 결정할 수 없으며 첫 번째 문자에서 문자열 끝까지 변수를 가져올 수 있습니다. 물론, 직렬 데이터 문자열은 변수 유형과 그 양에 따라 실제로 길어질 수 있지만 이것이 내가 사용하는 시스템이며 지금까지 내가 겪었던 유일한 단점은 직렬 길이이므로 유일한 단점입니다. 에 대해 알다.


x 개의 문자를 int / float / char에 저장하기 위해 어떤 종류의 함수를 사용합니까?
Steven10172

1
이것을 알지 못할 수도 있지만 설명하는 것은 정확히struct 메모리에서 패딩을 무시 하는 방식 이며, 사용하는 데이터 전송 기능이 Steven의 답변 에서 논의 된 기능과 유사하다고 생각합니다 .
asheeshr

@AsheeshR 나는 실제로 구조체가 그런 식일 수 있다고 생각했지만 구조체를 다시 포맷하려고 할 때 벽에 부딪 히고 반대편에서 다시 읽습니다. 그렇기 때문에이 문자열을 수행하여 잘못 읽었을 때 쉽게 디버깅 할 수 있고 "MOTORa023 MOTORb563"과 같이 지정하지 않으면 직렬 데이터를 직접 읽을 수도 있습니다. 공간.
Newbie97

@ Steven10172 잘 나는 특정 기능을 추적하지 않는다는 것을 인정한다. 오히려 매번 특정 기능을 구글한다. String to int, String to floatString 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);
}

파이썬 코드 :

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.