Gửi số lượng lớn dữ liệu nối tiếp


13

Vì vậy, trong các lĩnh vực robot đôi khi bạn cần nhiều bảng và hoặc máy tính được liên kết với nhau để chia sẻ thông tin hoặc lưu dữ liệu thống kê. Hiện tại tôi cần gửi một vài biến khác nhau qua kết nối nối tiếp và đang tự hỏi đâu là cách tốt nhất để thực hiện điều đó?

Cho đến nay tôi đã xác định rằng việc gửi cấu trúc có thể là cách dễ dàng hơn để gửi dữ liệu. Có ai biết cách nào khác có thể hiệu quả hơn không?

Xin lưu ý rằng về cơ bản tôi sẽ phải gửi dữ liệu cho 4 động cơ, máy nén, nhiệt độ khác nhau, những thứ ngẫu nhiên và 3 phần của cánh tay.

Câu trả lời:


9

Với suy nghĩ cá nhân của tôi về Structs là cách hiệu quả nhất để gửi nhiều biến khác nhau, tôi đã xây dựng một thư viện để giúp việc gửi các cấu trúc và biến qua serial dễ dàng hơn. Mã nguồn

Trong thư viện này, nó làm cho việc gửi qua nối tiếp dễ dàng. Tôi đã sử dụng với phần cứng và phần mềm nối tiếp. Thông thường, điều này được sử dụng kết hợp với xbee, vì vậy tôi có thể gửi dữ liệu không dây đến và từ robot.

Khi gửi dữ liệu, nó làm cho nó đơn giản vì nó cho phép bạn gửi một biến hoặc một cấu trúc (nó không quan tâm).

Dưới đây là một ví dụ về việc gửi một char đơn giản qua 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');

Ví dụ về việc gửi một int đơn giản qua 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');

Ví dụ về việc gửi một cấu trúc qua 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');

Nhận ví dụ:

Nhận một char được gửi qua 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');

Nhận một int được gửi qua 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');

Nhận cấu trúc được gửi qua 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');

Khi bạn đọc dữ liệu bằng cách sử dụng, StreamSend::receiveObject()bạn cần biết dữ liệu đó là TỐT, Không tìm thấy hoặc BAD.

Tốt = Thành công

Không tìm thấy = Không tìm thấy ký tự tiền tố trong khung hình được chỉ định

Xấu = Bằng cách nào đó đã tìm thấy một ký tự tiền tố, nhưng dữ liệu không còn nguyên vẹn. Thông thường nó có nghĩa là không có ký tự hậu tố được tìm thấy hoặc dữ liệu không phải là kích thước chính xác.

Kiểm tra tính hợp lệ của dữ liệu:

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

Lớp 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
Câu trả lời tất cả mã, như câu trả lời tất cả liên kết được khuyến khích. trừ khi mã của bạn có hàng tấn bình luận, tôi khuyên bạn nên đưa ra một số lời giải thích về những gì đang diễn ra
TheDoctor

@TheDoctor, tôi đã cập nhật mã. Cần có thêm ý kiến ​​ngay bây giờ
Steven10172

1

Nếu bạn thực sự muốn gửi nó nhanh , tôi khuyên bạn nên sử dụng Full Duplex serial (FDX). Đó là cùng một giao thức mà USB và ethernet sử dụng, và nó nhanh hơn UART rất nhiều. Nhược điểm là nó thường yêu cầu phần cứng bên ngoài để tạo điều kiện cho tốc độ dữ liệu cao. Tôi đã nghe nói rằng phần mềm mớiSreial hỗ trợ FDX, nhưng điều này có thể chậm hơn cả UART phần cứng. Để biết thêm về các giao thức truyền thông, xem Làm thế nào để kết nối hai Arduino mà không có lá chắn?


Điều này nghe có vẻ thú vị. Tôi sẽ phải nhìn xa hơn vào nó.
Steven10172

Làm thế nào " nối tiếp song công hoàn toàn " có thể "nhanh hơn UART" rất nhiều khi trên thực tế, đó là giao tiếp UART tiêu chuẩn?
David Cary

UART là một giao tiếp tốc độ cố định. FDX gửi dữ liệu nhanh nhất có thể và gửi lại dữ liệu không tạo ra nó.
TheDoctor

Tôi rất thích tìm hiểu thêm về giao thức này. Bạn có thể thêm một liên kết đến câu trả lời của bạn mô tả một giao thức nhanh hơn UART không? Bạn đang nói về ý tưởng chung về yêu cầu lặp lại tự động bằng ACK-NAK , hoặc có một số giao thức cụ thể mà bạn có trong đầu? Không ai trong số các tìm kiếm Google của tôi cho "FDX" hoặc "serial duplex đầy đủ" dường như khớp với mô tả của bạn.
David Cary

1

Gửi một cấu trúc khá đơn giản.

Bạn có thể khai báo cấu trúc như bình thường, sau đó sử dụng memcpy (@ myStabase, @ myArray) để sao chép dữ liệu sang một vị trí mới, sau đó sử dụng một cái gì đó tương tự như mã dưới đây để ghi dữ liệu dưới dạng dữ liệu.

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 

Sau đó, bạn có thể đính kèm một thói quen ngắt vào pin trên thiết bị khác thực hiện như sau:

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

attachInterrupt(0,readSerial,Rising);  

// nói với mcu gọi fxn khi pinhigh. Điều này sẽ xảy ra trong hầu như bất kỳ thời điểm nào. nếu điều đó là không mong muốn, hãy loại bỏ ngắt và chỉ cần xem các ký tự mới trong vòng điều hành chính của bạn (còn gọi là bỏ phiếu UART).

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

Cú pháp và sử dụng con trỏ sẽ cần một số đánh giá. Tôi đã kéo tất cả sáng hơn để tôi chắc chắn đoạn mã trên thậm chí sẽ không biên dịch, nhưng ý tưởng là có. Điền vào cấu trúc của bạn, sao chép nó, sử dụng tín hiệu ngoài băng để tránh lỗi khung, ghi dữ liệu. Mặt khác, nhận dữ liệu, sao chép nó vào một cấu trúc và sau đó dữ liệu có thể truy cập được thông qua các phương thức truy cập thành viên thông thường.

Việc sử dụng bitfield cũng sẽ hoạt động, chỉ cần lưu ý rằng các ngòi nổ sẽ xuất hiện ngược. Ví dụ, cố gắng viết 0011 1101, có thể dẫn đến 1101 0011 xuất hiện ở đầu kia nếu các máy khác nhau theo thứ tự byte.

Nếu tính toàn vẹn dữ liệu là quan trọng, bạn cũng có thể thêm một tổng kiểm tra để đảm bảo rằng bạn không sao chép dữ liệu rác bị sai lệch. Đây là kiểm tra nhanh chóng và hiệu quả mà tôi đề nghị.


1

Nếu bạn có thể chịu đựng được khối lượng dữ liệu, gỡ lỗi communicatons là rất dễ dàng hơn nhiều khi gửi chuỗi hơn khi gửi nhị phân; sprintf () / sscanf () và các biến thể của chúng là bạn của bạn ở đây. Kèm theo giao tiếp trong các chức năng chuyên dụng trong mô-đun riêng của họ (tệp .cpp); nếu bạn cần tối ưu hóa kênh sau - sau khi bạn có hệ thống hoạt động - bạn có thể thay thế mô-đun dựa trên chuỗi bằng một mã hóa cho các tin nhắn nhỏ hơn.

Bạn sẽ làm cho cuộc sống của bạn dễ dàng hơn rất nhiều nếu bạn giữ chặt các thông số kỹ thuật của giao thức truyền và giải thích chúng một cách lỏng lẻo hơn khi tiếp nhận, liên quan đến độ rộng trường, dấu phân cách, kết thúc dòng, số không đáng kể, sự hiện diện của +dấu hiệu, v.v.


Ban đầu mã được viết để gửi lại dữ liệu trong một vòng lặp ổn định của một khối vì vậy nó phải khá nhanh.
Steven10172

0

Tôi không có thông tin chính thức ở đây, nhưng theo kinh nghiệm của tôi, mọi thứ đã diễn ra khá hiệu quả khi tôi chọn một vị trí ký tự nhất định để chứa trạng thái của một biến, để bạn có thể chỉ định ba ký tự đầu tiên là nhiệt độ và tiếp theo ba là góc của một servo, và như vậy. Vào cuối gửi, tôi sẽ lưu các biến riêng lẻ và sau đó kết hợp chúng thành một chuỗi để gửi ser seri. Ở đầu nhận, tôi sẽ tách chuỗi, lấy ba ký tự đầu tiên và biến chúng thành bất kỳ loại biến nào tôi cần, sau đó thực hiện lại để lấy giá trị biến tiếp theo. Hệ thống này hoạt động tốt nhất khi bạn biết chắc chắn số lượng ký tự mà mỗi biến sẽ đảm nhận và bạn luôn tìm kiếm các biến giống nhau (mà tôi hy vọng là có sẵn) mỗi khi dữ liệu nối tiếp lặp lại.

Bạn có thể chọn một biến để đặt chiều dài cuối cùng không xác định và sau đó lấy biến đó từ ký tự đầu tiên đến cuối chuỗi. Cấp, chuỗi dữ liệu nối tiếp có thể rất dài tùy thuộc vào loại biến và số lượng tuyệt đối của chúng, nhưng đây là hệ thống tôi sử dụng và cho đến nay, nhược điểm duy nhất tôi gặp phải là độ dài nối tiếp, do đó, đó là nhược điểm duy nhất của tôi biết.


Bạn sử dụng loại chức năng nào để lưu x lượng ký tự vào int / float / char?
Steven10172

1
Bạn có thể không nhận ra điều này, nhưng những gì bạn mô tả chính xác là cách structtổ chức trong bộ nhớ (không tính đến phần đệm) và tôi tưởng tượng các hàm truyền dữ liệu mà bạn sử dụng sẽ giống với các hàm được thảo luận trong câu trả lời của Steven .
asheeshr

@AsheeshR Tôi thực sự có một cảm giác cấu trúc có thể là như vậy, nhưng cá nhân tôi có xu hướng va vào tường khi cố gắng định dạng lại các cấu trúc và sau đó đọc lại chúng ở phía bên kia. Đó là lý do tại sao tôi hình dung rằng tôi chỉ cần thực hiện chuỗi điều này, để tôi có thể dễ dàng gỡ lỗi nếu mọi thứ bị đọc sai, và tôi thậm chí có thể tự đọc dữ liệu nối tiếp nếu tôi chỉ định nó như "MOTORa023 MOTORb563", v.v. các không gian.
Newbie97

@ Steven10172 Tôi thừa nhận tôi không theo dõi các chức năng cụ thể, thay vào đó tôi google chức năng cụ thể mỗi lần. Chuỗi thành int, Chuỗi để nổiChuỗi thành char . Hãy nhớ rằng tôi sử dụng các phương thức này trong c ++ thông thường và bản thân tôi chưa thử chúng trong Arduino IDE.
Newbie97

0

Gửi dữ liệu cấu trúc qua nối tiếp

Không có gì lạ mắt. Gửi một cấu trúc. Nó sử dụng một ký tự thoát '^' để phân định dữ liệu.

Mã 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);
}

Mã 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.